├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── Dockerfile ├── LICENSE ├── LICENSE.md ├── Makefile ├── NAMESPACE ├── R ├── instruction.R ├── jvmrr_example.R ├── operation.R ├── read_class.R ├── utils-pipe.R └── utils.R ├── README.Rmd ├── README.md ├── codecov.yml ├── docker-compose.yml ├── inst └── java │ ├── Arith.class │ ├── FizzBuzz.class │ └── Hello.class ├── java ├── Arith.java ├── FizzBuzz.java └── Hello.java ├── man ├── execute.Rd ├── jvmrr_example.Rd ├── pipe.Rd └── read_class.Rd └── tests ├── testthat.R └── testthat ├── test-instruction.R ├── test-jvmrr_example.R ├── test-operation.R ├── test-read_class.R └── test-utils.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | ^README\.Rmd$ 3 | ^\.travis\.yml$ 4 | ^Makefile$ 5 | ^docker-compose\.yml$ 6 | ^codecov\.yml$ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rhistory 2 | .RData 3 | .Rproj.user 4 | README.html 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | cache: packages 5 | after_success: 6 | - Rscript -e 'covr::codecov()' 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: jvmrr 2 | Title: What the Package Does (One Line, Title Case) 3 | Version: 0.0.0.9000 4 | Authors@R: 5 | person(given = "First", 6 | family = "Last", 7 | role = c("aut", "cre"), 8 | email = "first.last@example.com", 9 | comment = c(ORCID = "YOUR-ORCID-ID")) 10 | Description: What the package does (one paragraph). 11 | License: MIT + file LICENSE 12 | Encoding: UTF-8 13 | LazyData: true 14 | Suggests: 15 | testthat (>= 2.1.0), 16 | covr 17 | RoxygenNote: 6.1.1 18 | Collate: 19 | 'utils.R' 20 | 'instruction.R' 21 | 'jvmrr_example.R' 22 | 'operation.R' 23 | 'read_class.R' 24 | 'utils-pipe.R' 25 | Imports: 26 | dequer, 27 | magrittr, 28 | purrr, 29 | rlang 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rocker/tidyverse:3.6.1 2 | 3 | RUN install2.r --error \ 4 | dequer \ 5 | && rm -rf /tmp/downloaded_packages/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: igjit 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 igjit 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | JAVAS = $(wildcard java/*.java) 2 | CLASSES = $(subst java/,inst/java/,$(JAVAS:.java=.class)) 3 | 4 | USERID = $(shell id -u) 5 | 6 | all: $(CLASSES) 7 | 8 | inst/java/%.class: java/%.java 9 | docker-compose run --rm -u $(USERID) jdk javac -d inst/java/ $^ 10 | 11 | README.md: README.Rmd 12 | docker-compose run --rm -u $(USERID) r Rscript -e 'devtools::build_readme()' 13 | 14 | clean: 15 | rm -f inst/java/*.class 16 | 17 | .PHONY: all clean 18 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(execute) 5 | export(jvmrr_example) 6 | export(read_class) 7 | import(dequer) 8 | import(purrr) 9 | importFrom(magrittr,"%>%") 10 | -------------------------------------------------------------------------------- /R/instruction.R: -------------------------------------------------------------------------------- 1 | #' @import purrr 2 | #' @include utils.R 3 | instruction_of <- function(opcode) { 4 | name <- opcode_name_of(opcode) 5 | if (!is.null(name)) instruction_set[[name]] 6 | } 7 | 8 | instruction <- function(name, opcode, arity) { 9 | structure(list(name = name, opcode = opcode, arity = arity), class = "instruction") 10 | } 11 | 12 | instruction_set <- list(instruction("bipush", 16, 1), 13 | instruction("ldc", 18, 1), 14 | instruction("iadd", 96, 0), 15 | instruction("isub", 100, 0), 16 | instruction("imul", 104, 0), 17 | instruction("idiv", 108, 0), 18 | instruction("irem", 112, 0), 19 | instruction("iinc", 132, 2), 20 | instruction("ifeq", 153, 2), 21 | instruction("ifne", 154, 2), 22 | instruction("iflt", 155, 2), 23 | instruction("ifge", 156, 2), 24 | instruction("ifgt", 157, 2), 25 | instruction("ifle", 158, 2), 26 | instruction("if_icmpeq", 159, 2), 27 | instruction("if_icmpne", 160, 2), 28 | instruction("if_icmplt", 161, 2), 29 | instruction("if_icmpge", 162, 2), 30 | instruction("if_icmpgt", 163, 2), 31 | instruction("if_icmple", 164, 2), 32 | instruction("goto", 167, 2), 33 | instruction("return", 177, 0), 34 | instruction("getstatic", 178, 2), 35 | instruction("invokevirtual", 182, 2)) 36 | 37 | instruction_set <- c(instruction_set, 38 | map2(paste0("iconst_", c("m1", 0:5)), 2:8, ~ instruction(.x, .y, 0)), 39 | map2(paste0("istore_", 0:3), 59:62, ~ instruction(.x, .y, 0)), 40 | map2(paste0("iload_", 0:3), 26:29, ~ instruction(.x, .y, 0))) %>% 41 | set_names(map_chr(., "name")) 42 | 43 | opcodes <- instruction_set %>% 44 | map_dbl("opcode") 45 | 46 | opcode_name_of <- name_lookup(opcodes) 47 | 48 | with_op <- function(...) { 49 | ops <- as.list(opcodes) 50 | rlang::enquos(...) %>% 51 | map(~ rlang::eval_tidy(., ops)) %>% 52 | flatten_dbl 53 | } 54 | -------------------------------------------------------------------------------- /R/jvmrr_example.R: -------------------------------------------------------------------------------- 1 | ##' Get path to example file 2 | ##' 3 | ##' @param name file name 4 | ##' @export 5 | jvmrr_example <- function(name = NULL) { 6 | if (is.null(name)) { 7 | dir(system.file("java", package = "jvmrr")) 8 | } else { 9 | system.file("java", name, package = "jvmrr", mustWork = TRUE) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /R/operation.R: -------------------------------------------------------------------------------- 1 | #' Execute Java class 2 | #' 3 | #' @import dequer 4 | #' @param class Java class 5 | #' @export 6 | execute <- function(class) { 7 | constant_pool <- class$constant_pool 8 | main_method <- class$methods %>% 9 | detect(~ .$name == "main") 10 | code <- main_method$attributes %>% 11 | detect(~ .$attribute_name == "Code") %>% 12 | .$code 13 | 14 | execute_code(code, constant_pool) 15 | } 16 | 17 | execute_code <- function(code, constant_pool) { 18 | env <- rlang::env(emptyenv(), pc = 1, stack = stack(), frame = list()) 19 | while (env$pc <= length(code)) { 20 | op <- read_operation(code, env) 21 | execute_operation(op, constant_pool, env) 22 | } 23 | } 24 | 25 | execute_operation <- function(op, constant_pool, env) { 26 | opcode_name <- opcode_name_of(op$opcode) 27 | func <- dispatch_table[[opcode_name]] 28 | if (is.null(func)) stop("Not implemented: ", opcode_name) 29 | func(op, constant_pool, env) 30 | } 31 | 32 | dispatch_table <- list( 33 | bipush = function (op, constant_pool, env) push(env$stack, op$operands), 34 | ldc = function (op, constant_pool, env) { 35 | index <- op$operands 36 | name <- constant_pool[[constant_pool[[index]]$string_index]]$bytes 37 | push(env$stack, name) 38 | }, 39 | getstatic = function (op, constant_pool, env) { 40 | cp_index <- as_u2(op$operands[1], op$operands[2]) 41 | symbol_name_index <- constant_pool[[cp_index]] 42 | cls <- constant_pool[[constant_pool[[symbol_name_index$class_index]]$name_index]]$bytes 43 | field <- constant_pool[[constant_pool[[symbol_name_index$name_and_type_index]]$name_index]]$bytes 44 | name <- paste(cls, field, sep = ".") 45 | push(env$stack, name) 46 | }, 47 | invokevirtual = function (op, constant_pool, env) { 48 | index <- as_u2(op$operands[1], op$operands[2]) 49 | callee <- constant_pool[[constant_pool[[index]]$name_and_type_index]] 50 | method_name <- constant_pool[[callee$name_index]]$bytes 51 | # TODO 52 | if (method_name != "println") stop("Not implemented: ", method_name) 53 | args <- pop(env$stack) 54 | object_name <- pop(env$stack) 55 | cat(args, "\n", sep = "") 56 | }, 57 | iinc = function (op, constant_pool, env) { 58 | index <- op$operands[1] 59 | const <- op$operands[2] 60 | env$frame[[index]] <- unname(env$frame[[index]] + const) 61 | }, 62 | goto = function (op, constant_pool, env) { 63 | adr <- env$pc - 3 64 | offset <- as_s2(op$operands[1], op$operands[2]) 65 | env$pc <- adr + offset 66 | }, 67 | return = function (...) NULL 68 | ) 69 | 70 | iconst_i <- map(-1:5, ~ function(op, constant_pool, env) push(env$stack, .)) %>% 71 | set_names(paste0("iconst_", c("m1", 0:5))) 72 | 73 | istore_n <- map(0:3, ~ function(op, constant_pool, env) env$frame[[.]] <- pop(env$stack)) %>% 74 | set_names(paste0("istore_", 0:3)) 75 | 76 | iload_n <- map(0:3, ~ function(op, constant_pool, env) push(env$stack, env$frame[[.]])) %>% 77 | set_names(paste0("iload_", 0:3)) 78 | 79 | int_arith_op <- list(iadd = `+`, 80 | isub = `-`, 81 | imul = `*`, 82 | idiv = `%/%`, 83 | irem = `%%`) 84 | int_arith <- map(int_arith_op, ~ function(op, constant_pool, env) { 85 | value2 <- pop(env$stack) 86 | value1 <- pop(env$stack) 87 | result <- .(value1, value2) 88 | push(env$stack, result) 89 | }) 90 | 91 | cond_op <- list(eq = `==`, 92 | ne = `!=`, 93 | lt = `<`, 94 | le = `<=`, 95 | gt = `>`, 96 | ge = `>=`) 97 | 98 | if_icmpcond <- map(cond_op, ~ function(op, constant_pool, env) { 99 | adr <- env$pc - 3 100 | offset <- as_s2(op$operands[1], op$operands[2]) 101 | value2 <- pop(env$stack) 102 | value1 <- pop(env$stack) 103 | if (.(value1, value2)) env$pc <- adr + offset 104 | }) %>% 105 | set_names(paste0("if_icmp", names(cond_op))) 106 | 107 | ifcond <- map(cond_op, ~ function(op, constant_pool, env) { 108 | adr <- env$pc - 3 109 | offset <- as_s2(op$operands[1], op$operands[2]) 110 | value <- pop(env$stack) 111 | if (.(value, 0)) env$pc <- adr + offset 112 | }) %>% 113 | set_names(paste0("if", names(cond_op))) 114 | 115 | dispatch_table <- c(dispatch_table, iconst_i, istore_n, iload_n, int_arith, if_icmpcond, ifcond) 116 | 117 | operation <- function(opcode, operands) { 118 | structure(list(opcode = opcode, operands = operands), class = "operation") 119 | } 120 | 121 | read_operation <- function(code, env) { 122 | pc <- env$pc 123 | opcode <- code[pc] 124 | inst <- instruction_of(opcode) 125 | if (is.null(inst)) stop("Unknown opcode: ", opcode) 126 | operands <- if (inst$arity > 0) code[(pc + 1):(pc + inst$arity)] 127 | env$pc <- pc + 1 + inst$arity 128 | operation(inst$opcode, operands) 129 | } 130 | 131 | as_u2 <- function(byte1, byte2) bitwShiftL(byte1, 8) + byte2 132 | 133 | as_s2 <- function(byte1, byte2) { 134 | u2 <- as_u2(byte1, byte2) 135 | bitwAnd(u2, 0x7fff) - bitwAnd(u2, 0x8000) 136 | } 137 | -------------------------------------------------------------------------------- /R/read_class.R: -------------------------------------------------------------------------------- 1 | cp_tags <- c(CONSTANT_Utf8 = 1, 2 | CONSTANT_Class = 7, 3 | CONSTANT_String = 8, 4 | CONSTANT_Fieldref = 9, 5 | CONSTANT_Methodref = 10, 6 | CONSTANT_NameAndType = 12) 7 | 8 | cp_tag_name_of <- name_lookup(cp_tags) 9 | 10 | #' Read Java class file 11 | #' 12 | #' @include utils.R 13 | #' @param con connection 14 | #' @export 15 | read_class <- function(con) { 16 | if (is.character(con)) { 17 | con <- file(con, "rb") 18 | on.exit(close(con)) 19 | } 20 | 21 | magic <- readBin(con, "raw", 4, NA, FALSE, "big") 22 | minor_version <- read_u2(con) 23 | major_version <- read_u2(con) 24 | constant_pool_count <- read_u2(con) 25 | constant_pool <- replicate(constant_pool_count - 1, read_cp_info(con), simplify = FALSE) 26 | access_flags <- read_u2(con) 27 | this_class <- read_u2(con) 28 | this_class_name <- constant_pool[[constant_pool[[this_class]]$name_index]]$bytes 29 | super_class <- read_u2(con) 30 | super_class_name <- constant_pool[[constant_pool[[super_class]]$name_index]]$bytes 31 | interfaces_count <- read_u2(con) 32 | # TODO 33 | if (interfaces_count > 0) stop() 34 | fields_count <- read_u2(con) 35 | # TODO 36 | if (fields_count > 0) stop() 37 | methods_count <- read_u2(con) 38 | methods <- replicate(methods_count, read_method_info(con, constant_pool), simplify = FALSE) 39 | attributes_count <- read_u2(con) 40 | attributes <- replicate(attributes_count, read_attribute(con, constant_pool), simplify = FALSE) 41 | 42 | list(magic = magic, 43 | minor_version = minor_version, 44 | major_version = major_version, 45 | constant_pool = constant_pool, 46 | access_flags = access_flags, 47 | this_class = this_class, 48 | this_class_name = this_class_name, 49 | super_class = super_class, 50 | super_class_name = super_class_name, 51 | methods = methods, 52 | attributes = attributes) 53 | } 54 | 55 | read_u1 <- function(con) readBin(con, "integer", 1, 1, FALSE, "big") 56 | 57 | read_u2 <- function(con) readBin(con, "integer", 1, 2, FALSE, "big") 58 | 59 | read_u4 <- function(con) { 60 | u2_1 <- read_u2(con) 61 | u2_2 <- read_u2(con) 62 | bitwShiftL(u2_1, 16) + u2_2 63 | } 64 | 65 | read_cp_info <- function(con) { 66 | tag <- read_u1(con) 67 | tag_name <- cp_tag_name_of(tag) 68 | info <- switch(tag_name, 69 | CONSTANT_Utf8 = { 70 | length <- read_u2(con) 71 | list(length = length, 72 | bytes = intToUtf8(readBin(con, "integer", length, 1, FALSE, "big"))) 73 | }, 74 | CONSTANT_Class = list(name_index = read_u2(con)), 75 | CONSTANT_String = list(string_index = read_u2(con)), 76 | CONSTANT_Fieldref = list(class_index = read_u2(con), name_and_type_index = read_u2(con)), 77 | CONSTANT_Methodref = list(class_index = read_u2(con), name_and_type_index = read_u2(con)), 78 | CONSTANT_NameAndType = list(name_index = read_u2(con), 79 | descriptor_index = read_u2(con))) 80 | 81 | c(tag = tag, info) 82 | } 83 | 84 | read_method_info <- function(con, constant_pool) { 85 | access_flags <- read_u2(con) 86 | name_index <- read_u2(con) 87 | name <- constant_pool[[name_index]]$bytes 88 | descriptor_index <- read_u2(con) 89 | descriptor <- constant_pool[[descriptor_index]]$bytes 90 | attributes_count <- read_u2(con) 91 | attributes <- replicate(attributes_count, read_attribute(con, constant_pool), simplify = FALSE) 92 | list(access_flags = access_flags, 93 | name_index = name_index, 94 | name = name, 95 | descriptor_index = descriptor_index, 96 | descriptor = descriptor, 97 | attributes_count = attributes_count, 98 | attributes = attributes) 99 | } 100 | 101 | read_attribute <- function(con, constant_pool) { 102 | attribute_name_index <- read_u2(con) 103 | attribute_length <- read_u4(con) 104 | attribute_name <- constant_pool[[attribute_name_index]]$bytes 105 | switch(attribute_name, 106 | Code = { 107 | max_stack <- read_u2(con) 108 | max_locals <- read_u2(con) 109 | code_length <- read_u4(con) 110 | code <- readBin(con, "integer", code_length, 1, FALSE, "big") 111 | exception_table_length <- read_u2(con) 112 | # TODO 113 | if (exception_table_length > 0) stop() 114 | exception_table <- list() 115 | attributes_count <- read_u2(con) 116 | attributes <- replicate(attributes_count, read_attribute(con, constant_pool), simplify = FALSE) 117 | list(attribute_name_index = attribute_name_index, 118 | attribute_name = attribute_name, 119 | attribute_length = attribute_length, 120 | max_stack = max_stack, 121 | max_locals = max_locals, 122 | code_length = code_length, 123 | code = code, 124 | exception_table_length = exception_table_length, 125 | exception_table = exception_table, 126 | attributes_count = attributes_count, 127 | attributes = attributes) 128 | }, 129 | LineNumberTable = { 130 | line_number_table_length <- read_u2(con) 131 | line_number_table <- replicate(line_number_table_length, 132 | list(start_pc = read_u2(con), 133 | line_number = read_u2(con)), 134 | simplify = FALSE) 135 | list(attribute_name_index = attribute_name_index, 136 | attribute_name = attribute_name, 137 | attribute_length = attribute_length, 138 | line_number_table_length = line_number_table_length, 139 | line_number_table = line_number_table) 140 | }, 141 | StackMapTable = { 142 | # TODO: parse stack_map_frame 143 | readBin(con, "integer", attribute_length, 1, FALSE, "big") 144 | list(attribute_name_index = attribute_name_index, 145 | attribute_name = attribute_name, 146 | attribute_length = attribute_length) 147 | }, 148 | SourceFile = list(attribute_name_index = attribute_name_index, 149 | attribute_name = attribute_name, 150 | sourcefile_index = read_u2(con)), 151 | stop("Not implemented: ", attribute_name)) 152 | } 153 | -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | NULL 12 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | name_lookup <- function(v) { 2 | function(x) { 3 | name <- names(which(v == x)) 4 | if (length(name) > 0) name 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # jvmrr 17 | 18 | 19 | [![Travis build status](https://travis-ci.org/igjit/jvmrr.svg?branch=master)](https://travis-ci.org/igjit/jvmrr) 20 | [![Codecov test coverage](https://codecov.io/gh/igjit/jvmrr/branch/master/graph/badge.svg)](https://codecov.io/gh/igjit/jvmrr?branch=master) 21 | 22 | 23 | jvmrr is a toy Java VM written in R. 24 | 25 | ## Installation 26 | 27 | You can install the development version of jvmrr from [GitHub](https://github.com/) with: 28 | 29 | ``` r 30 | # install.packages("remotes") 31 | remotes::install_github("igjit/jvmrr") 32 | ``` 33 | 34 | ## How to play 35 | 36 | ```{r} 37 | library(jvmrr) 38 | 39 | jvmrr_example() 40 | class_file <- jvmrr_example("FizzBuzz.class") 41 | java_class <- read_class(class_file) 42 | java_class %>% head(3) 43 | java_class %>% execute() 44 | ``` 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # jvmrr 5 | 6 | 7 | 8 | [![Travis build 9 | status](https://travis-ci.org/igjit/jvmrr.svg?branch=master)](https://travis-ci.org/igjit/jvmrr) 10 | [![Codecov test 11 | coverage](https://codecov.io/gh/igjit/jvmrr/branch/master/graph/badge.svg)](https://codecov.io/gh/igjit/jvmrr?branch=master) 12 | 13 | 14 | jvmrr is a toy Java VM written in R. 15 | 16 | ## Installation 17 | 18 | You can install the development version of jvmrr from 19 | [GitHub](https://github.com/) with: 20 | 21 | ``` r 22 | # install.packages("remotes") 23 | remotes::install_github("igjit/jvmrr") 24 | ``` 25 | 26 | ## How to play 27 | 28 | ``` r 29 | library(jvmrr) 30 | 31 | jvmrr_example() 32 | #> [1] "Arith.class" "FizzBuzz.class" "Hello.class" 33 | class_file <- jvmrr_example("FizzBuzz.class") 34 | java_class <- read_class(class_file) 35 | java_class %>% head(3) 36 | #> $magic 37 | #> [1] ca fe ba be 38 | #> 39 | #> $minor_version 40 | #> [1] 0 41 | #> 42 | #> $major_version 43 | #> [1] 55 44 | java_class %>% execute() 45 | #> 1 46 | #> 2 47 | #> Fizz 48 | #> 4 49 | #> Buzz 50 | #> Fizz 51 | #> 7 52 | #> 8 53 | #> Fizz 54 | #> Buzz 55 | #> 11 56 | #> Fizz 57 | #> 13 58 | #> 14 59 | #> FizzBuzz 60 | #> 16 61 | #> 17 62 | #> Fizz 63 | #> 19 64 | #> Buzz 65 | ``` 66 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | jdk: 4 | image: openjdk:11-slim 5 | volumes: 6 | - .:/opt/work 7 | working_dir: /opt/work 8 | r: 9 | build: . 10 | volumes: 11 | - .:/opt/work 12 | working_dir: /opt/work 13 | -------------------------------------------------------------------------------- /inst/java/Arith.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igjit/jvmrr/9bf7aa8fc01b825a11bad39894e8d95fe0f7aeb8/inst/java/Arith.class -------------------------------------------------------------------------------- /inst/java/FizzBuzz.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igjit/jvmrr/9bf7aa8fc01b825a11bad39894e8d95fe0f7aeb8/inst/java/FizzBuzz.class -------------------------------------------------------------------------------- /inst/java/Hello.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igjit/jvmrr/9bf7aa8fc01b825a11bad39894e8d95fe0f7aeb8/inst/java/Hello.class -------------------------------------------------------------------------------- /java/Arith.java: -------------------------------------------------------------------------------- 1 | public class Arith { 2 | public static void main (String[] args) { 3 | int a = 12; 4 | int b = 4; 5 | System.out.println(a * b - 6); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /java/FizzBuzz.java: -------------------------------------------------------------------------------- 1 | class FizzBuzz { 2 | public static void main(String[] args) { 3 | for (int i = 1; i <= 20; i++) { 4 | if (i % 15 == 0) { 5 | System.out.println("FizzBuzz"); 6 | } else if (i % 3 == 0) { 7 | System.out.println("Fizz"); 8 | } else if (i % 5 == 0) { 9 | System.out.println("Buzz"); 10 | } else { 11 | System.out.println(i); 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/Hello.java: -------------------------------------------------------------------------------- 1 | public class Hello { 2 | public static void main (String[] args) { 3 | System.out.println("Hello, world."); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /man/execute.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/operation.R 3 | \name{execute} 4 | \alias{execute} 5 | \title{Execute Java class} 6 | \usage{ 7 | execute(class) 8 | } 9 | \arguments{ 10 | \item{class}{Java class} 11 | } 12 | \description{ 13 | Execute Java class 14 | } 15 | -------------------------------------------------------------------------------- /man/jvmrr_example.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/jvmrr_example.R 3 | \name{jvmrr_example} 4 | \alias{jvmrr_example} 5 | \title{Get path to example file} 6 | \usage{ 7 | jvmrr_example(name = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{file name} 11 | } 12 | \description{ 13 | Get path to example file 14 | } 15 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/read_class.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/read_class.R 3 | \name{read_class} 4 | \alias{read_class} 5 | \title{Read Java class file} 6 | \usage{ 7 | read_class(con) 8 | } 9 | \arguments{ 10 | \item{con}{connection} 11 | } 12 | \description{ 13 | Read Java class file 14 | } 15 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(jvmrr) 3 | 4 | test_check("jvmrr") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-instruction.R: -------------------------------------------------------------------------------- 1 | test_that("instruction_of works", { 2 | expect_equal(instruction_of(18)$name, "ldc") 3 | }) 4 | 5 | test_that("with_op works", { 6 | n <- 4 7 | expect_equal(with_op(iinc, 2, n), c(132, 2, 4)) 8 | }) 9 | -------------------------------------------------------------------------------- /tests/testthat/test-jvmrr_example.R: -------------------------------------------------------------------------------- 1 | test_that("jvmrr_example works", { 2 | expect_match(jvmrr_example(), "\\.class") 3 | }) 4 | -------------------------------------------------------------------------------- /tests/testthat/test-operation.R: -------------------------------------------------------------------------------- 1 | test_that("read_operation works", { 2 | code <- c(18, 1, 178, 0, 2) 3 | env <- rlang::env(pc = 1) 4 | 5 | op <- read_operation(code, env) 6 | expect_equal(op, operation(18, 1)) 7 | expect_equal(env$pc, 3) 8 | 9 | op <- read_operation(code, env) 10 | expect_equal(op, operation(178, c(0, 2))) 11 | expect_equal(env$pc, 6) 12 | }) 13 | 14 | test_that("iconst_ works", { 15 | env <- rlang::env(pc = 1, stack = stack(), frame = list()) 16 | op <- read_operation(with_op(iconst_2), env) 17 | execute_operation(op, NULL, env) 18 | expect_equal(as.list(env$stack), list(2)) 19 | }) 20 | 21 | test_that("istore_ works", { 22 | env <- rlang::env(pc = 1, stack = as.stack(2), frame = list()) 23 | op <- read_operation(with_op(istore_1), env) 24 | execute_operation(op, NULL, env) 25 | expect_equal(env$frame, list(2)) 26 | }) 27 | 28 | test_that("iload_ works", { 29 | env <- rlang::env(pc = 1, stack = stack(), frame = list(2)) 30 | op <- read_operation(with_op(iload_1), env) 31 | execute_operation(op, NULL, env) 32 | expect_equal(as.list(env$stack), list(2)) 33 | }) 34 | 35 | test_that("isub works", { 36 | env <- rlang::env(pc = 1, stack = as.stack(list(4, 10)), frame = list()) 37 | op <- read_operation(with_op(isub), env) 38 | execute_operation(op, NULL, env) 39 | expect_equal(as.list(env$stack), list(6)) 40 | }) 41 | 42 | test_that("iinc works", { 43 | env <- rlang::env(pc = 1, stack = stack(), frame = list(10, 20)) 44 | op <- read_operation(with_op(iinc, 2, 4), env) 45 | execute_operation(op, NULL, env) 46 | expect_equal(env$frame, list(10, 24)) 47 | }) 48 | 49 | test_that("if_icmp works", { 50 | env <- rlang::env(pc = 1, stack = as.stack(list(3, 2)), frame = list()) 51 | op <- read_operation(with_op(if_icmplt, 0, 10), env) 52 | execute_operation(op, NULL, env) 53 | expect_equal(env$pc, 11) 54 | 55 | env <- rlang::env(pc = 1, stack = as.stack(list(2, 3)), frame = list()) 56 | op <- read_operation(with_op(if_icmplt, 0, 10), env) 57 | execute_operation(op, NULL, env) 58 | expect_equal(env$pc, 4) 59 | }) 60 | 61 | test_that("if works", { 62 | env <- rlang::env(pc = 1, stack = as.stack(0), frame = list()) 63 | op <- read_operation(with_op(ifeq, 0, 10), env) 64 | execute_operation(op, NULL, env) 65 | expect_equal(env$pc, 11) 66 | 67 | env <- rlang::env(pc = 1, stack = as.stack(1), frame = list()) 68 | op <- read_operation(with_op(ifeq, 0, 10), env) 69 | execute_operation(op, NULL, env) 70 | expect_equal(env$pc, 4) 71 | }) 72 | 73 | test_that("goto works", { 74 | env <- rlang::env(pc = 1, stack = stack(), frame = list()) 75 | op <- read_operation(with_op(goto, 0, 10), env) 76 | execute_operation(op, NULL, env) 77 | expect_equal(env$pc, 11) 78 | }) 79 | 80 | test_that("execute works", { 81 | file <- jvmrr_example("Hello.class") 82 | class <- read_class(file) 83 | expect_output(execute(class), "Hello, world.") 84 | 85 | file <- jvmrr_example("Arith.class") 86 | class <- read_class(file) 87 | expect_output(execute(class), "42") 88 | 89 | file <- jvmrr_example("FizzBuzz.class") 90 | class <- read_class(file) 91 | i <- 1:20 92 | fizzbuzz <- ifelse(i %% 15 == 0 , "FizzBuzz", 93 | ifelse(i %% 3 == 0, "Fizz", 94 | ifelse(i %% 5 == 0, "Buzz", i))) 95 | expect_output(execute(class), paste(fizzbuzz, collapse = "\n")) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/testthat/test-read_class.R: -------------------------------------------------------------------------------- 1 | test_that("read_class works", { 2 | file <- jvmrr_example("Hello.class") 3 | cls <- read_class(file) 4 | expect_equal(cls$magic, as.raw(c(0xca, 0xfe, 0xba, 0xbe))) 5 | expect_equal(cls$this_class_name, "Hello") 6 | expect_equal(cls$super_class_name, "java/lang/Object") 7 | }) 8 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("name_lookup works", { 2 | name_of <- name_lookup(c(a = 11, b = 12)) 3 | expect_equal(name_of(12), "b") 4 | expect_null(name_of(111)) 5 | }) 6 | --------------------------------------------------------------------------------