-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI work and early javax.script scaffolding
- Loading branch information
Showing
21 changed files
with
822 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
rootProject.name = 'asmble' | ||
|
||
rootProject.name = 'asmble' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
Oops, something went wrong.