@@ -14,6 +14,7 @@ enum VarRef {
1414 AI ( usize ) , // 0-based index into AI1..AI8
1515 AQ ( usize ) , // 0-based index into AQ1..AQ8
1616 Value ( usize ) , // 0-based index into value1..value5
17+ Tq , // TQ text output (numeric value in the simulator)
1718}
1819
1920#[ derive( Debug , Clone , Copy , PartialEq ) ]
@@ -59,15 +60,35 @@ enum SeqLine {
5960// Parser
6061// ============================================================================
6162
63+ /// Strip a trailing `//` line comment, respecting double-quoted strings so
64+ /// that a `//` inside a quoted TQ string is preserved. Returns the slice of
65+ /// `line` before the comment marker (caller should trim).
66+ fn strip_inline_comment ( line : & str ) -> & str {
67+ let bytes = line. as_bytes ( ) ;
68+ let mut in_str = false ;
69+ let mut i = 0 ;
70+ while i < bytes. len ( ) {
71+ match bytes[ i] {
72+ b'"' => in_str = !in_str,
73+ b'/' if !in_str && i + 1 < bytes. len ( ) && bytes[ i + 1 ] == b'/' => {
74+ return & line[ ..i] ;
75+ }
76+ _ => { }
77+ }
78+ i += 1 ;
79+ }
80+ line
81+ }
82+
6283/// Parse a multi-line program text. Each sequence is separated by
6384/// `sequence N` headers. If no header, everything is sequence 1.
6485fn parse_programs ( text : & str ) -> Vec < Vec < SeqLine > > {
6586 let mut programs: Vec < Vec < SeqLine > > = vec ! [ Vec :: new( ) ; 8 ] ;
6687 let mut current_seq: usize = 0 ; // 0-based
6788
6889 for raw_line in text. lines ( ) {
69- let line = raw_line. trim ( ) ;
70- if line. is_empty ( ) || line. starts_with ( "//" ) || line . starts_with ( '#' ) {
90+ let line = strip_inline_comment ( raw_line) . trim ( ) ;
91+ if line. is_empty ( ) || line. starts_with ( '#' ) {
7192 // If we're in a sequence, add a Comment line to preserve line numbering
7293 if !programs[ current_seq] . is_empty ( ) || current_seq > 0 {
7394 programs[ current_seq] . push ( SeqLine :: Comment ) ;
@@ -195,6 +216,15 @@ fn parse_set(s: &str) -> SeqLine {
195216 let target_str = s[ ..eq_pos] . trim ( ) ;
196217 let expr_str = s[ eq_pos + 1 ..] . trim ( ) ;
197218 if let Some ( target) = parse_varref ( target_str) {
219+ // TQ accepts text output: `set TQ = "label" AQ2`. In the numeric
220+ // simulator we drop quoted string literals and evaluate the
221+ // remaining numeric/IO tokens (matching the documented value).
222+ if target == VarRef :: Tq {
223+ return SeqLine :: Set {
224+ target,
225+ expr : parse_tq_value ( expr_str) ,
226+ } ;
227+ }
198228 if let Some ( expr) = parse_expr ( expr_str) {
199229 return SeqLine :: Set { target, expr } ;
200230 }
@@ -203,6 +233,26 @@ fn parse_set(s: &str) -> SeqLine {
203233 SeqLine :: Comment
204234}
205235
236+ /// Parse the right-hand side of `set TQ = ...`. Quoted string literals are
237+ /// stripped (they carry no numeric value in the simulator); whatever numeric
238+ /// expression remains is evaluated, defaulting to 0 when only text is present.
239+ fn parse_tq_value ( expr_str : & str ) -> Expr {
240+ let mut numeric = String :: new ( ) ;
241+ let mut in_str = false ;
242+ for c in expr_str. chars ( ) {
243+ match c {
244+ '"' => in_str = !in_str,
245+ _ if !in_str => numeric. push ( c) ,
246+ _ => { }
247+ }
248+ }
249+ let numeric = numeric. trim ( ) ;
250+ if numeric. is_empty ( ) {
251+ return Expr :: Lit ( 0.0 ) ;
252+ }
253+ parse_expr ( numeric) . unwrap_or ( Expr :: Lit ( 0.0 ) )
254+ }
255+
206256fn parse_waitcondition ( s : & str ) -> SeqLine {
207257 if let Some ( ( left, op, right) ) = parse_comparison ( s) {
208258 return SeqLine :: WaitCondition { left, op, right } ;
@@ -345,6 +395,10 @@ fn parse_varref(s: &str) -> Option<VarRef> {
345395 }
346396 }
347397 }
398+ // TQ text output
399+ if s == "tq" {
400+ return Some ( VarRef :: Tq ) ;
401+ }
348402 None
349403}
350404
@@ -437,9 +491,9 @@ pub fn validate_program(text: &str) -> Vec<ProgramError> {
437491
438492 for raw_line in text. lines ( ) {
439493 line_num += 1 ;
440- let trimmed = raw_line. trim ( ) ;
494+ let trimmed = strip_inline_comment ( raw_line) . trim ( ) ;
441495
442- if trimmed. is_empty ( ) || trimmed. starts_with ( "//" ) || trimmed . starts_with ( '#' ) {
496+ if trimmed. is_empty ( ) || trimmed. starts_with ( '#' ) {
443497 continue ;
444498 }
445499
@@ -695,6 +749,8 @@ fn validate_set_target(
695749 message : format ! ( "{} missing value after '='" , cmd) ,
696750 context : context. to_string ( ) ,
697751 } ) ;
752+ } else if target_str == "tq" && expr_str. contains ( '"' ) {
753+ // `set TQ = "text" ...` — quoted text output is valid; skip numeric check.
698754 } else if parse_expr ( expr_str) . is_none ( ) {
699755 // Try to give a more specific error
700756 let msg = validate_expr_detail ( expr_str, cmd) ;
@@ -906,6 +962,7 @@ pub struct SequenceController {
906962 prev_triggers : [ f64 ; 8 ] , // for rising edge detection on S1-S8
907963 prev_off : f64 ,
908964 skipping_if : usize , // depth of false-if blocks being skipped
965+ tq : f64 , // TQ text/value output (last value set by a sequence)
909966}
910967
911968impl SequenceController {
@@ -930,6 +987,7 @@ impl SequenceController {
930987 prev_triggers : [ 0.0 ; 8 ] ,
931988 prev_off : 0.0 ,
932989 skipping_if : 0 ,
990+ tq : 0.0 ,
933991 }
934992 }
935993
@@ -954,6 +1012,7 @@ impl SequenceController {
9541012 self . pulse_active = [ false ; 8 ] ;
9551013 self . skipping_if = 0 ;
9561014 self . time_since_step = 0.0 ;
1015+ self . tq = 0.0 ;
9571016 }
9581017
9591018 fn eval_expr ( & self , expr : & Expr , ai : & [ f64 ; 8 ] ) -> f64 {
@@ -984,13 +1043,15 @@ impl SequenceController {
9841043 VarRef :: AI ( i) => ai[ * i] ,
9851044 VarRef :: AQ ( i) => self . outputs [ * i] ,
9861045 VarRef :: Value ( i) => self . variables [ * i] ,
1046+ VarRef :: Tq => self . tq ,
9871047 }
9881048 }
9891049
9901050 fn write_var ( & mut self , vr : & VarRef , val : f64 ) {
9911051 match vr {
9921052 VarRef :: AQ ( i) => self . outputs [ * i] = val,
9931053 VarRef :: Value ( i) => self . variables [ * i] = val,
1054+ VarRef :: Tq => self . tq = val,
9941055 VarRef :: AI ( _) => { } // AI inputs are read-only
9951056 }
9961057 }
@@ -1215,7 +1276,7 @@ impl Block for SequenceController {
12151276 let mut out = self . outputs . to_vec ( ) ;
12161277 out. push ( 0.0 ) ; // current sequence
12171278 out. push ( 0.0 ) ; // current line
1218- out. push ( 0.0 ) ; // TQ
1279+ out. push ( self . tq ) ; // TQ
12191280 self . prev_triggers = triggers;
12201281 return out;
12211282 }
@@ -1278,7 +1339,7 @@ impl Block for SequenceController {
12781339 } else {
12791340 0.0
12801341 } ) ;
1281- out. push ( 0.0 ) ; // TQ — not implemented
1342+ out. push ( self . tq ) ; // TQ — last value/text assigned by a sequence
12821343
12831344 out
12841345 }
@@ -1294,13 +1355,14 @@ impl Block for SequenceController {
12941355 bool_signal ( self . waiting ) ,
12951356 self . time_since_step ,
12961357 self . interval ,
1358+ self . tq ,
12971359 ] ) ) ;
12981360 Some ( state)
12991361 }
13001362
13011363 fn restore ( & mut self , state : & [ u8 ] ) {
1302- // 8 outputs + 5 variables + 6 state fields = 19 f64s
1303- if let Some ( values) = deserialize_f64s ( state, 19 ) {
1364+ // 8 outputs + 5 variables + 7 state fields = 20 f64s
1365+ if let Some ( values) = deserialize_f64s ( state, 20 ) {
13041366 self . outputs . copy_from_slice ( & values[ 0 ..8 ] ) ;
13051367 self . variables . copy_from_slice ( & values[ 8 ..13 ] ) ;
13061368 self . current_seq = values[ 13 ] as usize ;
@@ -1309,6 +1371,7 @@ impl Block for SequenceController {
13091371 self . waiting = values[ 16 ] > 0.5 ;
13101372 self . time_since_step = values[ 17 ] ;
13111373 self . interval = values[ 18 ] ;
1374+ self . tq = values[ 19 ] ;
13121375 }
13131376 }
13141377
@@ -1888,4 +1951,122 @@ mod tests {
18881951 2
18891952 ) ;
18901953 }
1954+
1955+ // --- Inline comments & TQ output ---
1956+
1957+ #[ test]
1958+ fn parse_inline_comment_keeps_command ( ) {
1959+ // A trailing `// ...` comment must not swallow the command.
1960+ let programs = parse_programs ( "waitcondition AI2 > AI4 // wait until AI2 > AI4" ) ;
1961+ assert ! (
1962+ matches!( & programs[ 0 ] [ 0 ] , SeqLine :: WaitCondition { .. } ) ,
1963+ "inline comment should be stripped, leaving the waitcondition"
1964+ ) ;
1965+ }
1966+
1967+ #[ test]
1968+ fn parse_inline_comment_on_set ( ) {
1969+ let programs = parse_programs ( "set AQ1 = 5 // set output high" ) ;
1970+ assert ! ( matches!(
1971+ & programs[ 0 ] [ 0 ] ,
1972+ SeqLine :: Set {
1973+ target: VarRef :: AQ ( 0 ) ,
1974+ ..
1975+ }
1976+ ) ) ;
1977+ }
1978+
1979+ #[ test]
1980+ fn parse_full_line_comment_still_skipped ( ) {
1981+ // `// command` disables the whole line — nothing is parsed.
1982+ let programs = parse_programs ( "// set AQ1 = 5" ) ;
1983+ assert ! (
1984+ programs[ 0 ] . is_empty( ) ,
1985+ "full-line comment should be skipped"
1986+ ) ;
1987+ }
1988+
1989+ #[ test]
1990+ fn strip_inline_comment_respects_quotes ( ) {
1991+ // `//` inside a quoted string is preserved.
1992+ assert_eq ! (
1993+ strip_inline_comment( "set TQ = \" a//b\" // real comment" ) . trim( ) ,
1994+ "set TQ = \" a//b\" "
1995+ ) ;
1996+ assert_eq ! ( strip_inline_comment( "set AQ1 = 5" ) . trim( ) , "set AQ1 = 5" ) ;
1997+ }
1998+
1999+ #[ test]
2000+ fn parse_tq_numeric ( ) {
2001+ let programs = parse_programs ( "set TQ = AI1 + 1" ) ;
2002+ match & programs[ 0 ] [ 0 ] {
2003+ SeqLine :: Set { target, .. } => assert_eq ! ( * target, VarRef :: Tq ) ,
2004+ _ => panic ! ( "expected Set TQ" ) ,
2005+ }
2006+ }
2007+
2008+ #[ test]
2009+ fn parse_varref_tq ( ) {
2010+ assert_eq ! ( parse_varref( "tq" ) , Some ( VarRef :: Tq ) ) ;
2011+ assert_eq ! ( parse_varref( "TQ" ) , Some ( VarRef :: Tq ) ) ;
2012+ }
2013+
2014+ #[ test]
2015+ fn sequence_controller_tq_numeric_output ( ) {
2016+ // set TQ = AI1 * 2 → TQ output (index 10) tracks the value.
2017+ let mut block = SequenceController :: new ( "set TQ = AI1 * 2" , 100.0 ) ;
2018+ let mut ai = no_ai ( ) ;
2019+ ai[ 0 ] = 13.5 ;
2020+ let idle = make_inputs ( & no_triggers ( ) , & ai, 0.0 , 0.0 ) ;
2021+ let trig = make_inputs ( & trigger_seq ( 1 ) , & ai, 0.0 , 0.0 ) ;
2022+ let out = block. eval ( & trig, & [ ] , 0.0 , & idle) ;
2023+ assert_eq ! ( out[ 10 ] , 27.0 , "TQ output should be AI1*2" ) ;
2024+ }
2025+
2026+ #[ test]
2027+ fn sequence_controller_tq_text_output_uses_numeric_token ( ) {
2028+ // set TQ = "Value at AQ2 is" AQ2 → numeric part (AQ2) drives TQ.
2029+ let mut block =
2030+ SequenceController :: new ( "set AQ2 = 7\n set TQ = \" Value at AQ2 is\" AQ2" , 100.0 ) ;
2031+ let idle = make_inputs ( & no_triggers ( ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2032+ let trig = make_inputs ( & trigger_seq ( 1 ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2033+ let out = block. eval ( & trig, & [ ] , 0.0 , & idle) ;
2034+ assert_eq ! ( out[ 10 ] , 7.0 , "TQ should carry the numeric token value" ) ;
2035+ }
2036+
2037+ #[ test]
2038+ fn sequence_controller_tq_pure_text_is_zero ( ) {
2039+ // Pure text with no numeric token → TQ stays 0 but the line is valid.
2040+ let mut block = SequenceController :: new ( "set TQ = \" hello world\" " , 100.0 ) ;
2041+ assert ! ( matches!( & block. programs[ 0 ] [ 0 ] , SeqLine :: Set { .. } ) ) ;
2042+ let idle = make_inputs ( & no_triggers ( ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2043+ let trig = make_inputs ( & trigger_seq ( 1 ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2044+ let out = block. eval ( & trig, & [ ] , 0.0 , & idle) ;
2045+ assert_eq ! ( out[ 10 ] , 0.0 ) ;
2046+ }
2047+
2048+ #[ test]
2049+ fn validate_inline_comment_is_clean ( ) {
2050+ // A command with a trailing comment must not produce validation errors.
2051+ let errors = validate_program ( "set AQ1 = 5 // make it bright" ) ;
2052+ assert ! ( errors. is_empty( ) , "got: {:?}" , errors) ;
2053+ }
2054+
2055+ #[ test]
2056+ fn validate_tq_text_assignment_ok ( ) {
2057+ let errors = validate_program ( "set TQ = \" Value is\" AQ2" ) ;
2058+ assert ! ( errors. is_empty( ) , "got: {:?}" , errors) ;
2059+ }
2060+
2061+ #[ test]
2062+ fn sequence_controller_tq_state_roundtrip ( ) {
2063+ let mut block = SequenceController :: new ( "set TQ = 42" , 100.0 ) ;
2064+ let idle = make_inputs ( & no_triggers ( ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2065+ let trig = make_inputs ( & trigger_seq ( 1 ) , & no_ai ( ) , 0.0 , 0.0 ) ;
2066+ block. eval ( & trig, & [ ] , 0.0 , & idle) ;
2067+ let saved = block. state ( ) . expect ( "state" ) ;
2068+ let mut restored = SequenceController :: new ( "set TQ = 42" , 100.0 ) ;
2069+ restored. restore ( & saved) ;
2070+ assert_eq ! ( restored. tq, 42.0 ) ;
2071+ }
18912072}
0 commit comments