/** * Sealed Sins, 2023. */ import { VM, VmReg } from './vm'; describe('Virtual Machine', () => { it('is serializable', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['yld'], ['let', VmReg.R0, 2], ['yld'], ['let', VmReg.R0, 3], ]); vm.next(); const state = vm.save(); expect(vm.getReg(VmReg.R0)).toEqual(1); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(2); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(3); vm.load(state); expect(vm.getReg(VmReg.R0)).toEqual(1); }); it('is suitable for writing a visual novel', () => { const vm = new VM([ ['let', VmReg.R0, { name: '', text: '' }], ['sti', VmReg.R0, 'state'], // Mark page start. ['tag', 'pageStart:1'], ['yld'], // Load state & event info. ['ldi', VmReg.R0, 'state'], ['ldi', VmReg.R1, 'event'], // Check event type and proceed if it's `next`. ['get', VmReg.R2, VmReg.R1, 'type'], ['let', VmReg.R3, 'next'], ['jeq', VmReg.R2, VmReg.R3, 'pageUpdate:1'], ['jmp', 'pageStart:1'], // Update state. ['tag', 'pageUpdate:1'], ['let', VmReg.R4, { text: 'Hello!' }], ['mrg', VmReg.R0, VmReg.R4], // Mark next page start... ['tag', 'pageStart:2'], ['yld'], ]); vm.next(); expect(vm.getVar('state')).toEqual({ name: '', text: '' }); vm.setVar('event', { type: 'not-next' }); vm.next(); expect(vm.getVar('state')).toEqual({ name: '', text: '' }); vm.setVar('event', { type: 'next' }); vm.next(); expect(vm.getVar('state')).toEqual({ name: '', text: 'Hello!' }); }); it('implements "let" command', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, 2], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(1); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "mov" command', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, 2], ['mov', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(2); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "get" command', () => { const vm = new VM([ ['let', VmReg.R0, { a: 1, b: 2, c: [3, 4] }], ['get', VmReg.R1, VmReg.R0, 'a'], ['get', VmReg.R2, VmReg.R0, 'b'], ['get', VmReg.R3, VmReg.R0, 'c[0]'], ['get', VmReg.R4, VmReg.R0, 'c[1]'], ]); vm.next(); expect(vm.getReg(VmReg.R1)).toEqual(1); expect(vm.getReg(VmReg.R2)).toEqual(2); expect(vm.getReg(VmReg.R3)).toEqual(3); expect(vm.getReg(VmReg.R4)).toEqual(4); }); it('implements "set" command', () => { const vm = new VM([ ['let', VmReg.R0, {}], ['let', VmReg.R1, 1], ['let', VmReg.R2, 2], ['set', VmReg.R1, VmReg.R0, 'a'], ['set', VmReg.R2, VmReg.R0, 'b'], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual({ a: 1, b: 2 }); }); it('implements "mrg" command', () => { const vm = new VM([ ['let', VmReg.R0, { a: 1 }], ['let', VmReg.R1, { b: 2, c: 3 }], ['mrg', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual({ a: 1, b: 2, c: 3 }); expect(vm.getReg(VmReg.R1)).toEqual({ b: 2, c: 3 }); }); it('implements "ldi" command', () => { const vm = new VM([ ['ldi', VmReg.R0, 'a'], ['ldi', VmReg.R1, 'b'], ]); vm.setVar('a', 1); vm.setVar('b', 2); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(1); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "sti" command', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, 2], ['sti', VmReg.R0, 'a'], ['sti', VmReg.R1, 'b'], ]); vm.next(); expect(vm.getVar('a')).toEqual(1); expect(vm.getVar('b')).toEqual(2); }); it('implements "tag", "jmp" and "yld" commands', () => { const vm = new VM([ ['tag', 'start'], ['let', VmReg.R0, 0], ['yld'], ['let', VmReg.R0, 1], ['yld'], ['jmp', 'start'], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(0); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(1); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(0); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(1); }); it('implements "jeq" command', () => { const vm = new VM([ ['let', VmReg.R0, 0], ['let', VmReg.R1, 1], ['jeq', VmReg.R0, VmReg.R1, 'isEqual'], ['yld'], ['let', VmReg.R0, 1], ['jeq', VmReg.R0, VmReg.R1, 'isEqual'], ['yld'], ['tag', 'isEqual'], ['let', VmReg.R2, true], ]); vm.next(); expect(vm.getReg(VmReg.R2)).not.toEqual(true); vm.next(); expect(vm.getReg(VmReg.R2)).toEqual(true); }); it('implements "jgt" command', () => { const vm = new VM([ ['let', VmReg.R0, 0], ['let', VmReg.R1, 0], ['jgt', VmReg.R0, VmReg.R1, 'isGreater'], ['yld'], ['let', VmReg.R0, 1], ['jgt', VmReg.R0, VmReg.R1, 'isGreater'], ['yld'], ['tag', 'isGreater'], ['let', VmReg.R2, true], ]); vm.next(); expect(vm.getReg(VmReg.R2)).not.toEqual(true); vm.next(); expect(vm.getReg(VmReg.R2)).toEqual(true); }); it('implements "jlt" command', () => { const vm = new VM([ ['let', VmReg.R0, 0], ['let', VmReg.R1, 0], ['jlt', VmReg.R0, VmReg.R1, 'isSmaller'], ['yld'], ['let', VmReg.R1, 1], ['jlt', VmReg.R0, VmReg.R1, 'isSmaller'], ['yld'], ['tag', 'isSmaller'], ['let', VmReg.R2, true], ]); vm.next(); expect(vm.getReg(VmReg.R2)).not.toEqual(true); vm.next(); expect(vm.getReg(VmReg.R2)).toEqual(true); }); it('implements "dbg" command', () => { const logMock = jest.spyOn(console, 'log').mockImplementation(); const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, { a: 2, b: 3 }], ['sti', VmReg.R0, 'x'], ['sti', VmReg.R1, 'y'], ['dbg', '{{ reg.R0 }} {{ reg.R1.a }} {{ reg.R1.b }}'], ['dbg', '{{ mem.x }} {{ mem.y.a }} {{ mem.y.b }}'], ]); vm.next(); expect(logMock).toHaveBeenNthCalledWith(1, '1 2 3'); expect(logMock).toHaveBeenNthCalledWith(2, '1 2 3'); logMock.mockRestore(); }); it('implements "fmt" command', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, { a: 2, b: 3 }], ['sti', VmReg.R0, 'x'], ['sti', VmReg.R1, 'y'], ['fmt', VmReg.R3, '{{ reg.R0 }} {{ reg.R1.a }} {{ reg.R1.b }}'], ['fmt', VmReg.R4, '{{ mem.x }} {{ mem.y.a }} {{ mem.y.b }}'], ]); vm.next(); expect(vm.getReg(VmReg.R3)).toEqual('1 2 3'); expect(vm.getReg(VmReg.R4)).toEqual('1 2 3'); }); it('implements "not" command', () => { const vm = new VM([ ['let', VmReg.R0, true], ['not', VmReg.R0], ['yld'], ['not', VmReg.R0], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(false); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(true); }); it('implements "inc" command', () => { const vm = new VM([ ['let', VmReg.R0, 0], ['inc', VmReg.R0], ['inc', VmReg.R0], ['inc', VmReg.R0], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(+3); }); it('implements "dec" command', () => { const vm = new VM([ ['let', VmReg.R0, 0], ['dec', VmReg.R0], ['dec', VmReg.R0], ['dec', VmReg.R0], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(-3); }); it('implements "add" command', () => { const vm = new VM([ ['let', VmReg.R0, 1], ['let', VmReg.R1, 2], ['add', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(3); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "sub" command', () => { const vm = new VM([ ['let', VmReg.R0, 3], ['let', VmReg.R1, 2], ['sub', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(1); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "mul" command', () => { const vm = new VM([ ['let', VmReg.R0, 4], ['let', VmReg.R1, 2], ['mul', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(8); expect(vm.getReg(VmReg.R1)).toEqual(2); }); it('implements "div" command', () => { const vm = new VM([ ['let', VmReg.R0, 4], ['let', VmReg.R1, 2], ['div', VmReg.R0, VmReg.R1], ]); vm.next(); expect(vm.getReg(VmReg.R0)).toEqual(2); expect(vm.getReg(VmReg.R1)).toEqual(2); }); });