@@ -24,15 +24,15 @@ import type { FullDataHost } from '../helpers/full-data-host';
2424import { ScvdNode } from '../../model/scvd-node' ;
2525
2626class FakeNode extends ScvdNode {
27- constructor ( public readonly id : string , parent ?: ScvdNode , public value : string | number | bigint | Uint8Array | undefined = undefined , private members : Record < string , ScvdNode > = { } ) {
27+ constructor ( public readonly id : string , parent ?: ScvdNode , public value : EvalValue = undefined , private members : Record < string , ScvdNode > = { } ) {
2828 super ( parent ) ;
2929 }
30- public async setValue ( v : string | number ) : Promise < string | number | undefined > {
30+ public async setValue ( v : number | string ) : Promise < number | string | undefined > {
3131 this . value = v ;
3232 return v ;
3333 }
34- public async getValue ( ) : Promise < string | number | bigint | Uint8Array | undefined > {
35- return this . value ;
34+ public async getValue ( ) : Promise < string | number | bigint | Uint8Array < ArrayBufferLike > | undefined > {
35+ return this . value as unknown as string | number | bigint | Uint8Array < ArrayBufferLike > | undefined ;
3636 }
3737 public getSymbol ( name : string ) : ScvdNode | undefined {
3838 return this . members [ name ] ;
@@ -207,6 +207,33 @@ describe('evaluator coverage', () => {
207207 ( console . error as unknown as jest . Mock ) . mockRestore ( ) ;
208208 } ) ;
209209
210+ it ( 'applies member offsets when provided by host' , async ( ) => {
211+ class OffsetHost extends Host {
212+ async getMemberOffset ( ) : Promise < number | undefined > {
213+ return 8 ;
214+ }
215+ }
216+
217+ const base = new FakeNode ( 'base' ) ;
218+ const child = new FakeNode ( 'child' , base , 1 ) ;
219+ const obj = new FakeNode ( 'obj' , base , undefined , { child } ) ;
220+ const values = new Map < string , FakeNode > ( [ [ 'obj' , obj ] , [ 'child' , child ] ] ) ;
221+ const host = new OffsetHost ( values ) ;
222+ const ctx = new EvalContext ( { data : host , container : base } ) ;
223+
224+ await expect ( evalNode ( parseExpression ( 'obj.child' , false ) . ast , ctx ) ) . resolves . toBe ( child [ 'value' ] ) ;
225+ expect ( ctx . container . offsetBytes ) . toBe ( 8 ) ;
226+ } ) ;
227+
228+ it ( 'evaluates a complex conditional expression' , async ( ) => {
229+ const base = new FakeNode ( 'base' ) ;
230+ const count = new FakeNode ( 'Count' , base , 2 ) ;
231+ const values = new Map < string , FakeNode > ( [ [ 'Count' , count ] ] ) ;
232+ const host = new Host ( values ) ;
233+ const expr = '0==( (Count==0) || (Count==1) || (Count==8) || (Count==9) || (Count==10) )' ;
234+ await expect ( evalExpr ( expr , host , base ) ) . resolves . toBe ( 1 ) ;
235+ } ) ;
236+
210237 it ( 'covers intrinsics, pseudo members, and string/unary paths' , async ( ) => {
211238 const base = new FakeNode ( 'base' ) ;
212239 const arrElem = new FakeNode ( 'arr[0]' , base , 1 ) ;
@@ -468,4 +495,173 @@ describe('evaluator edge coverage', () => {
468495 host . formatPrintf = async ( ) => 'fmt' ;
469496 await expect ( evalNode ( printfAst , ctx ) ) . resolves . toContain ( 'fmt' ) ;
470497 } ) ;
498+
499+ it ( 'resolves array member via fast path with stride and element refs' , async ( ) => {
500+ const base = new BranchNode ( 'base' ) ;
501+ const member = new BranchNode ( 'm' , base , 42 ) ;
502+ const element = new BranchNode ( 'elem' , base , 0 , { m : member } ) ;
503+ const arr = new BranchNode ( 'arr' , base , undefined , { '1' : element } ) ;
504+ ( arr as unknown as { getElementRef ( ) : BranchNode } ) . getElementRef = ( ) => element ;
505+
506+ const values = new Map < string , BranchNode > ( [
507+ [ 'arr' , arr ] ,
508+ [ 'elem' , element ] ,
509+ [ 'm' , member ] ,
510+ ] ) ;
511+ const host = new BranchHost ( values ) ;
512+ host . getMemberOffset = async ( ) => 4 ;
513+ host . getElementStride = async ( ) => 8 ;
514+ host . getByteWidth = async ( ) => 4 ;
515+
516+ const ctx = new EvalContext ( { data : host , container : base } ) ;
517+ await expect ( evalNode ( parseExpression ( 'arr[1].m' , false ) . ast , ctx ) ) . resolves . toBe ( 42 ) ;
518+ } ) ;
519+
520+ it ( 'covers bigint comparisons and compound assignments' , async ( ) => {
521+ const base = new BranchNode ( 'base' ) ;
522+ const makeHost = ( entries : [ string , BranchNode ] [ ] ) => new BranchHost ( new Map ( entries ) ) ;
523+
524+ // Bigint comparisons exercise eq/lt/gte bigint paths and string equality coercion
525+ const bigA = new BranchNode ( 'bigA' , base , 5n ) ;
526+ const bigB = new BranchNode ( 'bigB' , base , 7n ) ;
527+ const compareCtx = new EvalContext ( { data : makeHost ( [ [ 'bigA' , bigA ] , [ 'bigB' , bigB ] ] ) , container : base } ) ;
528+ await expect ( evalNode ( parseExpression ( 'bigA == bigA' , false ) . ast , compareCtx ) ) . resolves . toBeTruthy ( ) ;
529+ await expect ( evalNode ( parseExpression ( 'bigA < bigB' , false ) . ast , compareCtx ) ) . resolves . toBeTruthy ( ) ;
530+ await expect ( evalNode ( parseExpression ( 'bigB >= bigA' , false ) . ast , compareCtx ) ) . resolves . toBeTruthy ( ) ;
531+ await expect ( evalNode ( parseExpression ( '"1" == 1' , false ) . ast , compareCtx ) ) . resolves . toBeTruthy ( ) ;
532+
533+ // Compound assignment operators cover arithmetic/bitwise branches
534+ const target = new BranchNode ( 'c' , base , 8 ) ;
535+ const other = new BranchNode ( 'd' , base , 3 ) ;
536+ const ctx = new EvalContext ( { data : makeHost ( [ [ 'c' , target ] , [ 'd' , other ] ] ) , container : base } ) ;
537+ const run = async ( expr : string , expected : number ) => {
538+ target . value = expr . startsWith ( 'c %=' )
539+ ? 9 // ensure a value divisible by d for %= branch
540+ : target . value ;
541+ await expect ( evalNode ( parseExpression ( expr , false ) . ast , ctx ) ) . resolves . toBe ( expected ) ;
542+ } ;
543+
544+ target . value = 8 ; await run ( 'c += d' , 11 ) ;
545+ target . value = 8 ; await run ( 'c -= d' , 5 ) ;
546+ target . value = 8 ; await run ( 'c *= d' , 24 ) ;
547+ target . value = 8 ; await run ( 'c /= d' , 2 ) ;
548+ target . value = 9 ; await run ( 'c %= d' , 0 ) ;
549+ target . value = 1 ; await run ( 'c <<= d' , 8 ) ;
550+ target . value = 8 ; await run ( 'c >>= d' , 1 ) ;
551+ target . value = 6 ; await run ( 'c &= d' , 2 ) ;
552+ target . value = 6 ; await run ( 'c ^= d' , 5 ) ;
553+ target . value = 6 ; await run ( 'c |= d' , 7 ) ;
554+ } ) ;
555+
556+ it ( 'normalizes scalar types that only provide a typename' , async ( ) => {
557+ const base = new BranchNode ( 'base' ) ;
558+ let sawTypename = false ;
559+ class TypenameHost extends BranchHost {
560+ async getValueType ( container : RefContainer ) : Promise < string | ScalarType | undefined > {
561+ const cur = container . current as BranchNode | undefined ;
562+ if ( cur ?. name === 'alias' ) {
563+ sawTypename = true ;
564+ return { kind : 'int' , bits : 16 , typename : 'alias_t' } ;
565+ }
566+ return super . getValueType ( container ) ;
567+ }
568+ }
569+
570+ const host = new TypenameHost ( new Map < string , BranchNode > ( [ [ 'alias' , new BranchNode ( 'alias' , base , 1 ) ] ] ) ) ;
571+ const ctx = new EvalContext ( { data : host , container : base } ) ;
572+ await expect ( evalNode ( parseExpression ( 'alias + 1' , false ) . ast , ctx ) ) . resolves . toBe ( 2 ) ;
573+ expect ( sawTypename ) . toBe ( true ) ;
574+ } ) ;
575+
576+ it ( 'recovers references across all findReferenceNode branches' , async ( ) => {
577+ class ClearingHost extends Host {
578+ async readValue ( container : RefContainer ) : Promise < EvalValue > {
579+ const v = await super . readValue ( container ) ;
580+ container . current = undefined ; // force recovery path
581+ return v ;
582+ }
583+ }
584+
585+ const base = new FakeNode ( 'base' ) ;
586+ const fn = new FakeNode ( 'fn' , base , ( ( n : number ) => n + 1 ) as unknown as EvalValue ) ;
587+ const val = new FakeNode ( 'v' , base , 1 ) ;
588+ const values = new Map < string , FakeNode > ( [ [ 'fn' , fn ] , [ 'v' , val ] , [ '__Running' , new FakeNode ( '__Running' , base , 1 ) ] ] ) ;
589+ const host = new ClearingHost ( values ) ;
590+ host . __Running = async ( ) => 1 ;
591+
592+ const ctx = new EvalContext ( { data : host , container : base } ) ;
593+
594+ const assignmentSeg = segFromAst ( parseExpression ( 'v = 2' , false ) . ast ) ;
595+ const callSeg = segFromAst ( parseExpression ( 'fn(3)' , false ) . ast ) ;
596+ const evalPointSeg : FormatSegment = {
597+ kind : 'FormatSegment' ,
598+ spec : 'd' ,
599+ value : { kind : 'EvalPointCall' , intrinsic : '__Running' , callee : { kind : 'Identifier' , name : '__Running' , start : 0 , end : 0 } as Identifier , args : [ ] , start : 0 , end : 0 } ,
600+ start : 0 ,
601+ end : 0 ,
602+ } ;
603+ const printfSeg = segFromAst ( {
604+ kind : 'PrintfExpression' ,
605+ segments : [ { kind : 'TextSegment' , text : 'x' , start : 0 , end : 0 } , segFromAst ( parseExpression ( 'v' , false ) . ast ) ] ,
606+ resultType : 'string' ,
607+ start : 0 ,
608+ end : 0 ,
609+ } as PrintfExpression ) ;
610+ const literalSeg = segFromAst ( { kind : 'NumberLiteral' , value : 9 , raw : '9' , valueType : 'number' , constValue : 9 , start : 0 , end : 1 } as ASTNode ) ;
611+
612+ await expect ( evalNode ( assignmentSeg , ctx ) ) . resolves . toBeDefined ( ) ;
613+ await expect ( evalNode ( callSeg , ctx ) ) . resolves . toBeDefined ( ) ;
614+ await expect ( evalNode ( evalPointSeg , ctx ) ) . resolves . toBeDefined ( ) ;
615+ await expect ( evalNode ( printfSeg , ctx ) ) . resolves . toBeDefined ( ) ;
616+ await expect ( evalNode ( literalSeg , ctx ) ) . resolves . toBeDefined ( ) ;
617+ } ) ;
618+
619+ it ( 'handles call expressions and read/write failures' , async ( ) => {
620+ class FnHost extends Host {
621+ async writeValue ( ) : Promise < EvalValue > {
622+ return undefined ;
623+ }
624+ }
625+ class UndefinedReadHost extends Host {
626+ async readValue ( ) : Promise < EvalValue > {
627+ return undefined ;
628+ }
629+ }
630+
631+ const base = new FakeNode ( 'base' ) ;
632+ const fnNode = new FakeNode ( 'fn' , base , ( ( a : number , b : number ) => a + b ) as unknown as EvalValue ) ;
633+ const valNode = new FakeNode ( 'x' , base , 1 ) ;
634+
635+ const fnHost = new FnHost ( new Map < string , FakeNode > ( [ [ 'fn' , fnNode ] , [ 'x' , valNode ] ] ) ) ;
636+ const fnCtx = new EvalContext ( { data : fnHost , container : base } ) ;
637+ await expect ( evalNode ( parseExpression ( 'fn(2, 3)' , false ) . ast , fnCtx ) ) . resolves . toBe ( 5 ) ;
638+ await expect ( evalNode ( parseExpression ( 'x(1)' , false ) . ast , fnCtx ) ) . rejects . toThrow ( 'Callee is not callable.' ) ;
639+ await expect ( evalNode ( parseExpression ( 'x = 2' , false ) . ast , fnCtx ) ) . rejects . toThrow ( 'Write returned undefined' ) ;
640+
641+ const readHost = new UndefinedReadHost ( new Map < string , FakeNode > ( [ [ 'x' , valNode ] ] ) ) ;
642+ const readCtx = new EvalContext ( { data : readHost , container : base } ) ;
643+ await expect ( evalNode ( parseExpression ( 'x' , false ) . ast , readCtx ) ) . rejects . toThrow ( 'Undefined value' ) ;
644+ } ) ;
645+
646+ it ( 'formats values across printf specs and NaN paths' , async ( ) => {
647+ const base = new BranchNode ( 'base' ) ;
648+ const host = new BranchHost ( new Map ( ) ) ;
649+ const ctx = new EvalContext ( { data : host , container : base } ) ;
650+
651+ const specs : Array < { spec : FormatSegment [ 'spec' ] ; ast : ASTNode ; expect : string } > = [
652+ { spec : '%' , ast : { kind : 'NumberLiteral' , value : 0 , raw : '0' , valueType : 'number' , constValue : 0 , start : 0 , end : 1 } , expect : '%' } ,
653+ { spec : 'd' , ast : { kind : 'NumberLiteral' , value : Number . POSITIVE_INFINITY , raw : 'inf' , valueType : 'number' , constValue : undefined , start : 0 , end : 1 } , expect : 'NaN' } ,
654+ { spec : 'u' , ast : { kind : 'NumberLiteral' , value : Number . NaN , raw : 'NaN' , valueType : 'number' , constValue : undefined , start : 0 , end : 1 } , expect : 'NaN' } ,
655+ { spec : 'u' , ast : { kind : 'NumberLiteral' , value : - 5 , raw : '-5' , valueType : 'number' , constValue : - 5 , start : 0 , end : 2 } , expect : String ( ( ( - 5 ) >>> 0 ) ) } ,
656+ { spec : 'x' , ast : { kind : 'NumberLiteral' , value : 255 , raw : '255' , valueType : 'number' , constValue : 255 , start : 0 , end : 3 } , expect : 'ff' } ,
657+ { spec : 't' , ast : { kind : 'BooleanLiteral' , value : false , valueType : 'boolean' , start : 0 , end : 1 } as ASTNode , expect : 'false' } ,
658+ { spec : 'S' , ast : { kind : 'StringLiteral' , value : 'hi' , raw : '"hi"' , valueType : 'string' , constValue : 'hi' , start : 0 , end : 2 } as ASTNode , expect : 'hi' } ,
659+ { spec : 'C' , ast : { kind : 'StringLiteral' , value : 'foo' , raw : '"foo"' , valueType : 'string' , constValue : 'foo' , start : 0 , end : 3 } as ASTNode , expect : 'foo' } ,
660+ ] ;
661+
662+ for ( const { spec, ast, expect : expected } of specs ) {
663+ const seg : FormatSegment = { kind : 'FormatSegment' , spec, value : ast , start : 0 , end : 0 } ;
664+ await expect ( evalNode ( seg , ctx ) ) . resolves . toBe ( expected ) ;
665+ }
666+ } ) ;
471667} ) ;
0 commit comments