about summary refs log tree commit diff
path: root/pkg/lang/vm/vm_test.go
blob: d63523f7d6413ab6e2aec9b7cdb70923a2d1fab1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
1
{ pkgs, ... }:

{
  imports = [
    ../../modules/home/common.nix
  ];

  home.stateVersion = "23.11";
}
/a> 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
package vm_test

import (
	"jinx/pkg/lang/vm"
	"jinx/pkg/lang/vm/code"
	"jinx/pkg/lang/vm/text"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestSimpleSub(t *testing.T) {
	src := `
	push_int 1
	push_int 2
	sub
	`

	test(t, src, "1")
}

func TestGetLocal(t *testing.T) {
	src := `
	push_int 404
	push_int 1
	push_int 2
	add
	get_local 1
	`

	test(t, src, "3")
}

func TestFibonacci(t *testing.T) {
	src := `
	# Array stored in local 0
	push_array 

	push_int 1
	get_local 0
	temp_arr_push

	push_int 1
	get_local 0
	temp_arr_push

	@fib_loop:

	push_int 1

	get_local 0
	temp_arr_len

	# Index of the last element stored in local 1
	sub

	# This is the last element
	get_local 1
	get_local 0
	# Store the last element in local 2
	index

	push_int 1
	get_local 1
	# Index of the second last element stored in local 2
	sub

	get_local 0
	# Store the second last element in local 3
	index

	add
	get_local 0
	temp_arr_push

	# Drop local 1, which was the length of the array, which we no longer need
	drop

	push_int 10

	get_local 0
	temp_arr_len

	# Jump if the array is larger than 10.
	lte
	jt @fib_loop
	`

	test(t, src, "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]")
}

func TestFunction(t *testing.T) {
	t.Skip("Reimplement arguments")

	src := `
	push_int 44
	push_function @subtract_two
	call 1
	halt

	@subtract_two:
		shift 1
		push_int 2
		get_local 0
		sub
		ret
	`

	test(t, src, "42")
}

func TestSimpleEnv(t *testing.T) {
	src := `
    push_int 404
    push_int 1
    push_int 405

    push_function @test
    # Add the local 1 to the environment.
    add_to_env 1
    call 0
    halt

    @test:
        push_int 2
        get_env 0
        add
        ret
    `

	test(t, src, "3")
}

func TestEscapedEnv(t *testing.T) {
	/*
	   fn create() {
	       var x = 0
	       return fn () {
	           x = x + 1
	           return x
	       }
	   }

	   var f = create()
	   var res = [f(), f(), f()]
	*/

	src := `    
    push_function @create
    call 0

    push_array

    get_local 0
    call 0
    get_local 1
    temp_arr_push

    get_local 0
    call 0
    get_local 1
    temp_arr_push
    
    get_local 0
    call 0
    get_local 1
    temp_arr_push
    halt

    @create:
        push_int 0
        push_int 404
        push_int 405
        push_function @create:anon_0
        add_to_env 0
        ret
    @create:anon_0:
        push_int 1
        get_env 0
        add
        set_env 0
        get_env 0
        ret
    `

	test(t, src, "[1, 2, 3]")
}

func TestMember(t *testing.T) {
	src := `
	push_array

	push_int 1
	get_local 0
	temp_arr_push

	push_int 2
	get_local 0
	temp_arr_push

	push_int 3
	get_local 0
	temp_arr_push

	get_member "length"
	call 0
	`

	test(t, src, "3")
}

func TestObject(t *testing.T) {
	src := `
	push_object

	push_string "Petronij"
	get_local 0
	set_member "name"

	push_int 10
	get_local 0
	set_member "age"

	get_local 0
	get_member "name"
	`

	test(t, src, "\"Petronij\"")
}

func TestTypeConstruct(t *testing.T) {
	/*
		type Cat {
			(name, age) {
				this.name = name
				this.age = age
			}

			fn meow(this) {
				return this.name + " says Meow!"
			}
		}

		var cat = Cat("Kitty", 3)
		cat.meow()
	*/

	src := `
	# Create a new type Cat
	push_type "Cat"

	push_function @Cat:$init
	push_string "$init"

	get_local 0
	get_member "$add_method"
	
	call 2

	push_function @Cat:meow
	push_string "meow"

	get_local 0
	get_member "$add_method"

	call 2
	
	# Create a new instance of Cat
	push_string "Kitty"
	push_int 3
	get_local 0
	get_member "$init"
	call 2

	# Call the meow method
	get_local 1
	get_member "meow"
	call 0
	halt

	@Cat:$init:
		get_env 0
		push_object
		anchor_type

		get_local 0
		get_local 2
		set_member "name"

		get_local 1
		get_local 2
		set_member "age"

		ret
	@Cat:meow:
		push_string " says Meow!"
		
		get_env 0
		get_member "name"

		add
		ret
	`

	test(t, src, "\"Kitty says Meow!\"")
}

func TestPrimes(t *testing.T) {
	/*
		var i = 0
		var primes = []

		for i <= 10 {
			var factors = []

			var j = 1
			for j <= i {
				if i % j <= 0 {
					factors.push(j)
				}
				j = j + 1
			}

			if factors.length() <= 2 {
				primes.push(i)
			}

			i = i + 1
		}

		print(primes)
	*/

	// TODO: This test uses OpLte instead of OpEq, which works,
	// but once OpEq is implemented, this test should be updated.

	src := `
	push_int 2
	push_array
	@main_loop:
		push_int 10
		get_local 0
		lte
		jf @end

		push_array
		push_int 1

	@factor_loop:
		get_local 0
		get_local 3
		lte
		jf @prime_check

		push_int 0

		get_local 3
		get_local 0
		mod

		lte
		jf @factor_loop_inc

		get_local 3
		get_local 2
		temp_arr_push

	@factor_loop_inc:
		push_int 1
		get_local 3
		add
		set_local 3
		jmp @factor_loop

	@prime_check:
		push_int 2
		get_local 2
		get_member "length"
		call 0
		lte

		jf @main_loop_inc

		get_local 0
		get_local 1
		temp_arr_push

	@main_loop_inc:
		push_int 1
		get_local 0
		add
		set_local 0

		drop
		drop

		jmp @main_loop
	@end:
		halt
	`

	test(t, src, "[2, 3, 5, 7]")
}

func test(t *testing.T, src string, expected string) {
	bc := compile(t, src)
	vm := vm.New(&bc)
	err := vm.Run()
	require.NoError(t, err)

	res, err := vm.GetResult()
	require.NoError(t, err)

	require.Equal(t, expected, res)
}

func compile(t *testing.T, src string) code.Code {
	comp := text.NewCompiler(strings.NewReader(src))
	bc, err := comp.Compile()
	require.NoErrorf(t, err, "compilation from text format failed: %s", err)

	return bc
}