Skip to content

Commit

Permalink
CLI work and early javax.script scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
cretz committed Apr 17, 2017
1 parent 34a49bf commit 786c920
Show file tree
Hide file tree
Showing 21 changed files with 822 additions and 34 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'asmble'
version '1.0-SNAPSHOT'
version '0.1.0'

buildscript {
ext.kotlin_version = '1.1.1'
Expand Down
3 changes: 1 addition & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
rootProject.name = 'asmble'

rootProject.name = 'asmble'
185 changes: 185 additions & 0 deletions src/main/kotlin/asmble/cli/Command.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package asmble.cli

import asmble.util.Logger

abstract class Command<T> {

// Can't have this delegate
// Ug: http://stackoverflow.com/questions/33966186/how-to-delegate-implementation-to-a-property-in-kotlin
lateinit var logger: Logger

abstract val name: String
abstract val desc: String

abstract fun args(bld: ArgsBuilder): T
abstract fun run(args: T)

fun runWithArgs(bld: ArgsBuilder) = run(args(bld))

interface ArgsBuilder {

fun arg(
name: String,
desc: String,
opt: String? = null,
default: String? = null,
lowPriority: Boolean = false
): String

fun args(
name: String,
desc: String,
opt: String? = null,
default: List<String>? = null,
lowPriority: Boolean = false
): List<String>

fun flag(opt: String, desc: String, lowPriority: Boolean = false): Boolean

fun done()

class ActualArgBuilder(var args: List<String>) : ArgsBuilder {

fun getArg(opt: String?) =
if (opt != null) args.indexOf("-$opt").takeIf { it != -1 }?.let { index ->
args.getOrNull(index + 1)?.also {
args = args.subList(0, index) + args.subList(index + 2, args.size)
}
} else args.indexOfFirst { !it.startsWith("-") || it == "--" }.takeIf { it != -1 }?.let { index ->
args[index].also { args = args.subList(0, index) + args.subList(index + 1, args.size) }
}

override fun arg(name: String, desc: String, opt: String?, default: String?, lowPriority: Boolean) =
getArg(opt) ?: default ?: error("Arg '$name' not found")

override fun args(
name: String,
desc: String,
opt: String?,
default: List<String>?,
lowPriority: Boolean
): List<String> {
var ret = emptyList<String>()
while (true) { ret += getArg(opt) ?: break }
return if (ret.isNotEmpty()) ret else default ?: error("Arg '$name' not found")
}

override fun flag(opt: String, desc: String, lowPriority: Boolean) =
args.indexOf("-$opt").takeIf { it != -1 }?.also {
args = args.subList(0, it) + args.subList(it + 1, args.size)
} != null

override fun done() =
require(args.isEmpty()) { "Unknown args: $args" }
}

class ArgDefBuilder : ArgsBuilder {
var argDefs = emptyList<ArgDef>(); private set

override fun arg(name: String, desc: String, opt: String?, default: String?, lowPriority: Boolean): String {
argDefs += ArgDef.WithValue(
name = name,
opt = opt,
desc = desc,
defaultDesc = default,
multi = false,
lowPriority = lowPriority
)
return default ?: ""
}

override fun args(
name: String,
desc: String,
opt: String?,
default: List<String>?,
lowPriority: Boolean
): List<String> {
argDefs += ArgDef.WithValue(
name = name,
opt = opt,
desc = desc,
defaultDesc = default?.let {
if (it.isEmpty()) "<empty>" else if (it.size == 1) it.first() else it.toString()
},
multi = true,
lowPriority = lowPriority
)
return default ?: emptyList()
}

override fun flag(opt: String, desc: String, lowPriority: Boolean): Boolean {
argDefs += ArgDef.Flag(
opt = opt,
desc = desc,
multi = false,
lowPriority = lowPriority
)
return false
}

override fun done() { }
}

sealed class ArgDef : Comparable<ArgDef> {
abstract val name: String
// True means it won't appear in the single-line desc
abstract val lowPriority: Boolean
abstract fun argString(bld: StringBuilder): StringBuilder
abstract fun descString(bld: StringBuilder): StringBuilder

override fun compareTo(other: ArgDef) = name.compareTo(other.name)

data class WithValue(
override val name: String,
// No opt means trailing
val opt: String?,
val desc: String,
val defaultDesc: String?,
val multi: Boolean,
override val lowPriority: Boolean
) : ArgDef() {
override fun argString(bld: StringBuilder): StringBuilder {
if (defaultDesc != null) bld.append('[')
if (opt != null) bld.append("-$opt ")
bld.append("<$name>")
if (defaultDesc != null) bld.append(']')
if (multi) bld.append("...")
return bld
}

override fun descString(bld: StringBuilder): StringBuilder {
if (opt != null) bld.append("-$opt ")
bld.append("<$name>")
bld.append(" - $desc ")
if (multi) bld.append("Multiple allowed. ")
if (defaultDesc != null) bld.append("Optional, default: $defaultDesc")
else bld.append("Required.")
return bld
}
}

// fun flag(opt: String, desc: String, lowPriority: Boolean = false): Boolean
data class Flag(
val opt: String,
val desc: String,
val multi: Boolean,
override val lowPriority: Boolean
) : ArgDef() {
override val name get() = opt

override fun argString(bld: StringBuilder): StringBuilder {
bld.append("[-$opt]")
if (multi) bld.append("...")
return bld
}

override fun descString(bld: StringBuilder): StringBuilder {
bld.append("-$opt - $desc Optional. ")
if (multi) bld.append("Multiple allowed. ")
return bld
}
}
}
}
}
71 changes: 71 additions & 0 deletions src/main/kotlin/asmble/cli/Compile.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package asmble.cli

import asmble.ast.Script
import asmble.compile.jvm.AstToAsm
import asmble.compile.jvm.ClsContext
import asmble.compile.jvm.withComputedFramesAndMaxs
import org.objectweb.asm.ClassWriter
import java.io.FileOutputStream

open class Compile : Command<Compile.Args>() {

override val name = "compile"
override val desc = "Compile WebAssembly to class file"

override fun args(bld: Command.ArgsBuilder) = Args(
inFile = bld.arg(
name = "inFile",
desc = "The wast or wasm WebAssembly file name. Can be '--' to read from stdin."
),
inFormat = bld.arg(
name = "inFormat",
opt = "format",
desc = "Either 'wast' or 'wasm' to describe format.",
default = "<use file extension>"
),
outClass = bld.arg(
name = "outClass",
desc = "The fully qualified class name."
),
outFile = bld.arg(
name = "outFile",
opt = "out",
desc = "The file name to output to. Can be '--' to write to stdout.",
default = "<outClass.class>"
)
).also { bld.done() }

override fun run(args: Args) {
// Get format
val inFormat =
if (args.inFormat != "<use file extension>") args.inFormat
else args.inFile.substringAfterLast('.', "<unknown>")
val script = Translate.inToAst(args.inFile, inFormat)
val mod = (script.commands.firstOrNull() as? Script.Cmd.Module)?.module ?:
error("Only a single sexpr for (module) allowed")
val outStream = when (args.outFile) {
"<outClass.class>" -> FileOutputStream(args.outClass.substringAfterLast('.') + ".class")
"--" -> System.out
else -> FileOutputStream(args.outFile)
}
outStream.use { outStream ->
val ctx = ClsContext(
packageName = if (!args.outClass.contains('.')) "" else args.outClass.substringBeforeLast('.'),
className = args.outClass.substringAfterLast('.'),
mod = mod,
logger = logger
)
AstToAsm.fromModule(ctx)
outStream.write(ctx.cls.withComputedFramesAndMaxs())
}
}

data class Args(
val inFile: String,
val inFormat: String,
val outClass: String,
val outFile: String
)

companion object : Compile()
}
36 changes: 36 additions & 0 deletions src/main/kotlin/asmble/cli/Help.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package asmble.cli

open class Help : Command<Help.Args>() {

override val name = "help"
override val desc = "Show command help"

override fun args(bld: ArgsBuilder) = Args(
command = bld.arg(
name = "command",
desc = "The command to see details for."
)
).also { bld.done() }

override fun run(args: Args) {
val command = commands.find { it.name == args.command } ?: error("Unable to find command '${args.command}'")
val argDefBld = ArgsBuilder.ArgDefBuilder()
Main.globalArgs(argDefBld)
command.args(argDefBld)

val out = StringBuilder()
out.appendln("Command: ${command.name}")
out.appendln("Description: ${command.desc}")
out.appendln("Usage:")
out.append(" ${command.name} ")
argDefBld.argDefs.forEach { if (!it.lowPriority) it.argString(out).append(' ') }
out.appendln().appendln()
out.append("Args:")
argDefBld.argDefs.sorted().forEach { out.appendln().append(" ").let(it::descString) }
println(out)
}

data class Args(val command: String)

companion object : Help()
}
74 changes: 74 additions & 0 deletions src/main/kotlin/asmble/cli/Invoke.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package asmble.cli

import asmble.compile.jvm.javaIdent

open class Invoke : ScriptCommand<Invoke.Args>() {

override val name = "invoke"
override val desc = "Invoke WebAssembly function"

override fun args(bld: Command.ArgsBuilder) = Args(
scriptArgs = scriptArgs(bld),
module = bld.arg(
name = "module",
opt = "mod",
desc = "The module name to run. If it's a JVM class, it must have a no-arg constructor.",
default = "<last-in-entry>"
),
export = bld.arg(
name = "export",
desc = "The specific export function to invoke.",
default = "<start-func>"
),
args = bld.args(
name = "arg",
desc = "Parameter for the export if export is present.",
default = emptyList()
),
resultToStdout = bld.flag(
opt = "res",
desc = "If there is a result, print it.",
lowPriority = true
)
).also { bld.done() }

override fun run(args: Args) {
val ctx = prepareContext(args.scriptArgs)
// Instantiate the module
val module =
if (args.module == "<last-in-entry>") ctx.modules.lastOrNull() ?: error("No modules available")
else ctx.registrations[args.module] ?: error("Unable to find module registered as ${args.module}")
// Just make sure the module is instantiated here...
module.instance(ctx)
// If an export is provided, call it
if (args.export != "<start-func>") args.export.javaIdent.let { javaName ->
val method = module.cls.declaredMethods.find { it.name == javaName } ?:
error("Unable to find export '${args.export}'")
// Map args to params
require(method.parameterTypes.size == args.args.size) {
"Given arg count of ${args.args.size} is invalid for $method"
}
val params = method.parameterTypes.withIndex().zip(args.args) { (index, paramType), arg ->
when (paramType) {
Int::class.java -> arg.toIntOrNull() ?: error("Arg ${index + 1} of '$arg' not int")
Long::class.java -> arg.toLongOrNull() ?: error("Arg ${index + 1} of '$arg' not long")
Float::class.java -> arg.toFloatOrNull() ?: error("Arg ${index + 1} of '$arg' not float")
Double::class.java -> arg.toDoubleOrNull() ?: error("Arg ${index + 1} of '$arg' not double")
else -> error("Unrecognized type for param ${index + 1}: $paramType")
}
}
val result = method.invoke(module.instance(ctx), *params.toTypedArray())
if (args.resultToStdout && method.returnType != Void.TYPE) println(result)
}
}

data class Args(
val scriptArgs: ScriptCommand.ScriptArgs,
val module: String,
val export: String,
val args: List<String>,
val resultToStdout: Boolean
)

companion object : Invoke()
}
Loading

0 comments on commit 786c920

Please sign in to comment.