diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..da0c7d6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/test/resources/spec"] + path = src/test/resources/spec + url = https://github.com/WebAssembly/spec.git diff --git a/src/main/kotlin/asmble/ast/Node.kt b/src/main/kotlin/asmble/ast/Node.kt new file mode 100644 index 0000000..1ea0539 --- /dev/null +++ b/src/main/kotlin/asmble/ast/Node.kt @@ -0,0 +1,558 @@ +package asmble.ast + +import kotlin.reflect.KClass + +sealed class Node { + data class Module( + val types: List, + val imports: List, + val tables: List, + val memories: List, + val globals: List, + val exports: List, + val startFuncIndex: Int?, + val elems: List, + val funcs: List, + val data: List, + val customSections: List + ) : Node() + + enum class ExternalKind { + FUNCTION, TABLE, MEMORY, GLOBAL + } + + enum class ElemType { + ANYFUNC + } + + sealed class Type : Node() { + + sealed class Value : Type() { + object I32 : Value() + object I64 : Value() + object F32 : Value() + object F64 : Value() + } + + data class Func( + val params: List, + val ret: Value? + ) : Type() + + data class Global( + val contentType: Value, + val mutable: Boolean + ) : Type() + + data class Table( + val elemType: ElemType, + val limits: ResizableLimits + ) : Type() + + data class Memory( + val limits: ResizableLimits + ) : Type() + } + + data class ResizableLimits( + val initial: Int, + val maximum: Int? + ) + + data class Import( + val module: String, + val field: String, + val kind: Kind + ) : Node() { + sealed class Kind { + data class Func(val typeIndex: Int) : Kind() + data class Table(val type: Type.Table) : Kind() + data class Memory(val type: Type.Memory) : Kind() + data class Global(val type: Type.Global) : Kind() + } + } + + data class Global( + val type: Type.Global, + val init: List + ) : Node() + + data class Export( + val field: String, + val kind: ExternalKind, + val index: Int + ) : Node() + + data class Elem( + val index: Int, + val offset: List, + val funcIndices: List + ) : Node() + + data class Func( + val type: Type.Func, + val locals: List, + val instructions: List + ) : Node() + + data class Data( + val index: Int, + val offset: List, + val data: Array + ) : Node() + + data class CustomSection( + val sectionIndex: Int, + val name: String, + val payload: Array + ) : Node() + + sealed class Instr : Node() { + // Control flow + object Unreachable : Instr() + object Nop : Instr() + data class Block(val type: Type.Value?) : Instr() + data class Loop(val type: Type.Value?) : Instr() + data class If(val type: Type.Value?) : Instr() + object Else : Instr() + object End : Instr() + data class Br(val relativeDepth: Int) : Instr() + data class BrIf(val relativeDepth: Int) : Instr() + data class BrTable( + val targetTable: List, + val default: Int + ) : Instr() + object Return : Instr() + + // Call operators + data class Call(val funcIndex: Int) : Instr() + data class CallIndirect( + val typeIndex: Int, + val reserved: Boolean + ) : Instr() + + // Parametric operators + object Drop : Instr() + object Select : Instr() + + // Variable access + data class GetLocal(val index: Int) : Instr() + data class SetLocal(val index: Int) : Instr() + data class TeeLocal(val index: Int) : Instr() + data class GetGlobal(val index: Int) : Instr() + data class SetGlobal(val index: Int) : Instr() + + // Memory operators + data class I32Load(val flags: Int, val offset: Int) : Instr() + data class I64Load(val flags: Int, val offset: Int) : Instr() + data class F32Load(val flags: Int, val offset: Int) : Instr() + data class F64Load(val flags: Int, val offset: Int) : Instr() + data class I32Load8S(val flags: Int, val offset: Int) : Instr() + data class I32Load8U(val flags: Int, val offset: Int) : Instr() + data class I32Load16S(val flags: Int, val offset: Int) : Instr() + data class I32Load16U(val flags: Int, val offset: Int) : Instr() + data class I64Load8S(val flags: Int, val offset: Int) : Instr() + data class I64Load8U(val flags: Int, val offset: Int) : Instr() + data class I64Load16S(val flags: Int, val offset: Int) : Instr() + data class I64Load16U(val flags: Int, val offset: Int) : Instr() + data class I64Load32S(val flags: Int, val offset: Int) : Instr() + data class I64Load32U(val flags: Int, val offset: Int) : Instr() + data class I32Store(val flags: Int, val offset: Int) : Instr() + data class I64Store(val flags: Int, val offset: Int) : Instr() + data class F32Store(val flags: Int, val offset: Int) : Instr() + data class F64Store(val flags: Int, val offset: Int) : Instr() + data class I32Store8(val flags: Int, val offset: Int) : Instr() + data class I32Store16(val flags: Int, val offset: Int) : Instr() + data class I64Store8(val flags: Int, val offset: Int) : Instr() + data class I64Store16(val flags: Int, val offset: Int) : Instr() + data class I64Store32(val flags: Int, val offset: Int) : Instr() + data class CurrentMemory(val reserved: Boolean) : Instr() + data class GrowMemory(val reserved: Boolean) : Instr() + + // Constants + data class I32Const(val value: Int) : Instr() + data class I64Const(val value: Long) : Instr() + data class F32Const(val value: Float) : Instr() + data class F64Const(val value: Double) : Instr() + + // Comparison operators + object I32Eqz : Instr() + object I32Eq : Instr() + object I32Ne : Instr() + object I32LtS : Instr() + object I32LtU : Instr() + object I32GtS : Instr() + object I32GtU : Instr() + object I32LeS : Instr() + object I32LeU : Instr() + object I32GeS : Instr() + object I32GeU : Instr() + object I64Eqz : Instr() + object I64Eq : Instr() + object I64Ne : Instr() + object I64LtS : Instr() + object I64LtU : Instr() + object I64GtS : Instr() + object I64GtU : Instr() + object I64LeS : Instr() + object I64LeU : Instr() + object I64GeS : Instr() + object I64GeU : Instr() + object F32Eq : Instr() + object F32Ne : Instr() + object F32Lt : Instr() + object F32Gt : Instr() + object F32Le : Instr() + object F32Ge : Instr() + object F64Eq : Instr() + object F64Ne : Instr() + object F64Lt : Instr() + object F64Gt : Instr() + object F64Le : Instr() + object F64Ge : Instr() + + // Numeric operators + object I32Clz : Instr() + object I32Ctz : Instr() + object I32Popcnt : Instr() + object I32Add : Instr() + object I32Sub : Instr() + object I32Mul : Instr() + object I32DivS : Instr() + object I32DivU : Instr() + object I32RemS : Instr() + object I32RemU : Instr() + object I32And : Instr() + object I32Or : Instr() + object I32Xor : Instr() + object I32Shl : Instr() + object I32ShrS : Instr() + object I32ShrU : Instr() + object I32Rotl : Instr() + object I32Rotr : Instr() + object I64Clz : Instr() + object I64Ctz : Instr() + object I64Popcnt : Instr() + object I64Add : Instr() + object I64Sub : Instr() + object I64Mul : Instr() + object I64DivS : Instr() + object I64DivU : Instr() + object I64RemS : Instr() + object I64RemU : Instr() + object I64And : Instr() + object I64Or : Instr() + object I64Xor : Instr() + object I64Shl : Instr() + object I64ShrS : Instr() + object I64ShrU : Instr() + object I64Rotl : Instr() + object I64Rotr : Instr() + object F32Abs : Instr() + object F32Neg : Instr() + object F32Ceil : Instr() + object F32Floor : Instr() + object F32Trunc : Instr() + object F32Nearest : Instr() + object F32Sqrt : Instr() + object F32Add : Instr() + object F32Sub : Instr() + object F32Mul : Instr() + object F32Div : Instr() + object F32Min : Instr() + object F32Max : Instr() + object F32CopySign : Instr() + object F64Abs : Instr() + object F64Neg : Instr() + object F64Ceil : Instr() + object F64Floor : Instr() + object F64Trunc : Instr() + object F64Nearest : Instr() + object F64Sqrt : Instr() + object F64Add : Instr() + object F64Sub : Instr() + object F64Mul : Instr() + object F64Div : Instr() + object F64Min : Instr() + object F64Max : Instr() + object F64CopySign : Instr() + + // Conversions + object I32WrapI64 : Instr() + object I32TruncSF32 : Instr() + object I32TruncUF32 : Instr() + object I32TruncSF64 : Instr() + object I32TruncUF64 : Instr() + object I64ExtendSI32 : Instr() + object I64ExtendUI32 : Instr() + object I64TruncSF32 : Instr() + object I64TruncUF32 : Instr() + object I64TruncSF64 : Instr() + object I64TruncUF64 : Instr() + object F32ConvertSI32 : Instr() + object F32ConvertUI32 : Instr() + object F32ConvertSI64 : Instr() + object F32ConvertUI64 : Instr() + object F32DemoteF64 : Instr() + object F64ConvertSI32 : Instr() + object F64ConvertUI32 : Instr() + object F64ConvertSI64 : Instr() + object F64ConvertUI64 : Instr() + object F64PromoteF32 : Instr() + + // Reinterpretations + object I32ReinterpretF32 : Instr() + object I64ReinterpretF64 : Instr() + object F32ReinterpretI32 : Instr() + object F64ReinterpretI64 : Instr() + } + + sealed class InstrOp(name: String) { + + sealed class ControlFlowOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : ControlFlowOp(name) + data class TypeArg(val name: String, val create: (Type.Value?) -> Instr) : ControlFlowOp(name) + data class DepthArg(val name: String, val create: (Int) -> Instr) : ControlFlowOp(name) + data class TableArg(val name: String, val create: (List, Int) -> Instr) : ControlFlowOp(name) + } + + sealed class CallOp(name: String) : InstrOp(name) { + data class IndexArg(val name: String, val create: (Int) -> Instr) : CallOp(name) + data class IndexReservedArg(val name: String, val create: (Int, Boolean) -> Instr) : CallOp(name) + } + + sealed class ParamOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : ParamOp(name) + } + + sealed class VarOp(name: String) : InstrOp(name) { + data class IndexArg(val name: String, val create: (Int) -> Instr) : VarOp(name) + } + + sealed class MemOp(name: String) : InstrOp(name) { + data class FlagsOffsetArg(val name: String, val create: (Int, Int) -> Instr) : MemOp(name) + data class ReservedArg(val name: String, val create: (Boolean) -> Instr) : MemOp(name) + } + + sealed class ConstOp(name: String) : InstrOp(name) { + data class IntArg(val name: String, val create: (Int) -> Instr) : ConstOp(name) + data class LongArg(val name: String, val create: (Long) -> Instr) : ConstOp(name) + data class FloatArg(val name: String, val create: (Float) -> Instr) : ConstOp(name) + data class DoubleArg(val name: String, val create: (Double) -> Instr) : ConstOp(name) + } + + sealed class CompareOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : CompareOp(name) + } + + sealed class NumOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : NumOp(name) + } + + sealed class ConvertOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : ConvertOp(name) + } + + sealed class ReinterpretOp(name: String) : InstrOp(name) { + data class NoArg(val name: String, val create: Instr) : ReinterpretOp(name) + } + + companion object { + // TODO: why can't I set a val in init? + var strToOpMap = emptyMap(); private set + var classToOpMap = emptyMap, InstrOp>(); private set + + init { + // Can't use reification here because inline funcs not allowed in nested context :-( + fun opMapEntry(name: String, newOp: (String, T) -> InstrOp, create: T, clazz: KClass) { + require(!strToOpMap.contains(name) && !classToOpMap.contains(clazz)) + val op = newOp(name, create) + strToOpMap += name to op + classToOpMap += clazz to op + } + + opMapEntry("unreachable", ControlFlowOp::NoArg, Instr.Unreachable, Instr.Unreachable::class) + opMapEntry("nop", ControlFlowOp::NoArg, Instr.Nop, Instr.Nop::class) + opMapEntry("block", ControlFlowOp::TypeArg, Instr::Block, Instr.Block::class) + opMapEntry("loop", ControlFlowOp::TypeArg, Instr::Loop, Instr.Loop::class) + opMapEntry("if", ControlFlowOp::TypeArg, Instr::If, Instr.If::class) + opMapEntry("else", ControlFlowOp::NoArg, Instr.Else, Instr.Else::class) + opMapEntry("end", ControlFlowOp::NoArg, Instr.End, Instr.End::class) + opMapEntry("br", ControlFlowOp::DepthArg, Instr::Br, Instr.Br::class) + opMapEntry("br_if", ControlFlowOp::DepthArg, Instr::BrIf, Instr.BrIf::class) + opMapEntry("br_if", ControlFlowOp::TableArg, Instr::BrTable, Instr.BrTable::class) + opMapEntry("return", ControlFlowOp::NoArg, Instr.Return, Instr.Return::class) + + opMapEntry("call", CallOp::IndexArg, Instr::Call, Instr.Call::class) + opMapEntry("call_indirect", CallOp::IndexReservedArg, Instr::CallIndirect, Instr.CallIndirect::class) + + opMapEntry("drop", ParamOp::NoArg, Instr.Drop, Instr.Drop::class) + opMapEntry("select", ParamOp::NoArg, Instr.Select, Instr.Drop::class) + + opMapEntry("get_local", VarOp::IndexArg, Instr::GetLocal, Instr.GetLocal::class) + opMapEntry("set_local", VarOp::IndexArg, Instr::SetLocal, Instr.SetLocal::class) + opMapEntry("tee_local", VarOp::IndexArg, Instr::TeeLocal, Instr.TeeLocal::class) + opMapEntry("get_global", VarOp::IndexArg, Instr::GetGlobal, Instr.GetGlobal::class) + opMapEntry("set_global", VarOp::IndexArg, Instr::SetGlobal, Instr.SetGlobal::class) + + opMapEntry("i32.load", MemOp::FlagsOffsetArg, Instr::I32Load, Instr.I32Load::class) + opMapEntry("i64.load", MemOp::FlagsOffsetArg, Instr::I64Load, Instr.I64Load::class) + opMapEntry("f32.load", MemOp::FlagsOffsetArg, Instr::F32Load, Instr.F32Load::class) + opMapEntry("f64.load", MemOp::FlagsOffsetArg, Instr::F64Load, Instr.F64Load::class) + opMapEntry("i32.load8_s", MemOp::FlagsOffsetArg, Instr::I32Load8S, Instr.I32Load8S::class) + opMapEntry("i32.load8_u", MemOp::FlagsOffsetArg, Instr::I32Load8U, Instr.I32Load8U::class) + opMapEntry("i32.load16_s", MemOp::FlagsOffsetArg, Instr::I32Load16S, Instr.I32Load16S::class) + opMapEntry("i32.load16_u", MemOp::FlagsOffsetArg, Instr::I32Load16U, Instr.I32Load16U::class) + opMapEntry("i64.load8_s", MemOp::FlagsOffsetArg, Instr::I64Load8S, Instr.I64Load8S::class) + opMapEntry("i64.load8_u", MemOp::FlagsOffsetArg, Instr::I64Load8U, Instr.I64Load8U::class) + opMapEntry("i64.load16_s", MemOp::FlagsOffsetArg, Instr::I64Load16S, Instr.I64Load16S::class) + opMapEntry("i64.load16_u", MemOp::FlagsOffsetArg, Instr::I64Load16U, Instr.I64Load16U::class) + opMapEntry("i64.load32_s", MemOp::FlagsOffsetArg, Instr::I64Load32S, Instr.I64Load32S::class) + opMapEntry("i64.load32_u", MemOp::FlagsOffsetArg, Instr::I64Load32U, Instr.I64Load32U::class) + opMapEntry("i32.store", MemOp::FlagsOffsetArg, Instr::I32Store, Instr.I32Store::class) + opMapEntry("i64.store", MemOp::FlagsOffsetArg, Instr::I64Store, Instr.I64Store::class) + opMapEntry("f32.store", MemOp::FlagsOffsetArg, Instr::F32Store, Instr.F32Store::class) + opMapEntry("f64.store", MemOp::FlagsOffsetArg, Instr::F64Store, Instr.F64Store::class) + opMapEntry("i32.store8", MemOp::FlagsOffsetArg, Instr::I32Store8, Instr.I32Store8::class) + opMapEntry("i32.store16", MemOp::FlagsOffsetArg, Instr::I32Store16, Instr.I32Store16::class) + opMapEntry("i64.store8", MemOp::FlagsOffsetArg, Instr::I64Store8, Instr.I64Store8::class) + opMapEntry("i64.store16", MemOp::FlagsOffsetArg, Instr::I64Store16, Instr.I64Store16::class) + opMapEntry("i64.store32", MemOp::FlagsOffsetArg, Instr::I64Store32, Instr.I64Store32::class) + opMapEntry("current_memory", MemOp::ReservedArg, Instr::CurrentMemory, Instr.CurrentMemory::class) + opMapEntry("grow_memory", MemOp::ReservedArg, Instr::GrowMemory, Instr.GrowMemory::class) + + opMapEntry("i32.const", ConstOp::IntArg, Instr::I32Const, Instr.I32Const::class) + opMapEntry("i64.const", ConstOp::LongArg, Instr::I64Const, Instr.I64Const::class) + opMapEntry("f32.const", ConstOp::FloatArg, Instr::F32Const, Instr.F32Const::class) + opMapEntry("f64.const", ConstOp::DoubleArg, Instr::F64Const, Instr.F64Const::class) + + opMapEntry("i32.eqz", CompareOp::NoArg, Instr.I32Eqz, Instr.I32Eqz::class) + opMapEntry("i32.eq", CompareOp::NoArg, Instr.I32Eq, Instr.I32Eq::class) + opMapEntry("i32.ne", CompareOp::NoArg, Instr.I32Ne, Instr.I32Ne::class) + opMapEntry("i32.lt_s", CompareOp::NoArg, Instr.I32LtS, Instr.I32LtS::class) + opMapEntry("i32.lt_u", CompareOp::NoArg, Instr.I32LtU, Instr.I32LtU::class) + opMapEntry("i32.gt_s", CompareOp::NoArg, Instr.I32GtS, Instr.I32GtS::class) + opMapEntry("i32.gt_u", CompareOp::NoArg, Instr.I32GtU, Instr.I32GtU::class) + opMapEntry("i32.le_s", CompareOp::NoArg, Instr.I32LeS, Instr.I32LeS::class) + opMapEntry("i32.le_u", CompareOp::NoArg, Instr.I32LeU, Instr.I32LeU::class) + opMapEntry("i32.ge_s", CompareOp::NoArg, Instr.I32GeS, Instr.I32GeS::class) + opMapEntry("i32.ge_u", CompareOp::NoArg, Instr.I32GeU, Instr.I32GeU::class) + opMapEntry("i64.eqz", CompareOp::NoArg, Instr.I64Eqz, Instr.I64Eqz::class) + opMapEntry("i64.eq", CompareOp::NoArg, Instr.I64Eq, Instr.I64Eq::class) + opMapEntry("i64.ne", CompareOp::NoArg, Instr.I64Ne, Instr.I64Ne::class) + opMapEntry("i64.lt_s", CompareOp::NoArg, Instr.I64LtS, Instr.I64LtS::class) + opMapEntry("i64.lt_u", CompareOp::NoArg, Instr.I64LtU, Instr.I64LtU::class) + opMapEntry("i64.gt_s", CompareOp::NoArg, Instr.I64GtS, Instr.I64GtS::class) + opMapEntry("i64.gt_u", CompareOp::NoArg, Instr.I64GtU, Instr.I64GtU::class) + opMapEntry("i64.le_s", CompareOp::NoArg, Instr.I64LeS, Instr.I64LeS::class) + opMapEntry("i64.le_u", CompareOp::NoArg, Instr.I64LeU, Instr.I64LeU::class) + opMapEntry("i64.ge_s", CompareOp::NoArg, Instr.I64GeS, Instr.I64GeS::class) + opMapEntry("i64.ge_u", CompareOp::NoArg, Instr.I64GeU, Instr.I64GeU::class) + opMapEntry("f32.eq", CompareOp::NoArg, Instr.F32Eq, Instr.F32Eq::class) + opMapEntry("f32.ne", CompareOp::NoArg, Instr.F32Ne, Instr.F32Ne::class) + opMapEntry("f32.lt", CompareOp::NoArg, Instr.F32Lt, Instr.F32Lt::class) + opMapEntry("f32.gt", CompareOp::NoArg, Instr.F32Gt, Instr.F32Gt::class) + opMapEntry("f32.le", CompareOp::NoArg, Instr.F32Le, Instr.F32Le::class) + opMapEntry("f32.ge", CompareOp::NoArg, Instr.F32Ge, Instr.F32Ge::class) + opMapEntry("f64.eq", CompareOp::NoArg, Instr.F64Eq, Instr.F64Eq::class) + opMapEntry("f64.ne", CompareOp::NoArg, Instr.F64Ne, Instr.F64Ne::class) + opMapEntry("f64.lt", CompareOp::NoArg, Instr.F64Lt, Instr.F64Lt::class) + opMapEntry("f64.gt", CompareOp::NoArg, Instr.F64Gt, Instr.F64Gt::class) + opMapEntry("f64.le", CompareOp::NoArg, Instr.F64Le, Instr.F64Le::class) + opMapEntry("f64.ge", CompareOp::NoArg, Instr.F64Ge, Instr.F64Ge::class) + + opMapEntry("i32.clz", NumOp::NoArg, Instr.I32Clz, Instr.I32Clz::class) + opMapEntry("i32.ctz", NumOp::NoArg, Instr.I32Ctz, Instr.I32Ctz::class) + opMapEntry("i32.popcnt", NumOp::NoArg, Instr.I32Popcnt, Instr.I32Popcnt::class) + opMapEntry("i32.add", NumOp::NoArg, Instr.I32Add, Instr.I32Add::class) + opMapEntry("i32.sub", NumOp::NoArg, Instr.I32Sub, Instr.I32Sub::class) + opMapEntry("i32.mul", NumOp::NoArg, Instr.I32Mul, Instr.I32Mul::class) + opMapEntry("i32.div_s", NumOp::NoArg, Instr.I32DivS, Instr.I32DivS::class) + opMapEntry("i32.div_u", NumOp::NoArg, Instr.I32DivU, Instr.I32DivU::class) + opMapEntry("i32.rem_s", NumOp::NoArg, Instr.I32RemS, Instr.I32RemS::class) + opMapEntry("i32.rem_u", NumOp::NoArg, Instr.I32RemU, Instr.I32RemU::class) + opMapEntry("i32.and", NumOp::NoArg, Instr.I32And, Instr.I32And::class) + opMapEntry("i32.or", NumOp::NoArg, Instr.I32Or, Instr.I32Or::class) + opMapEntry("i32.xor", NumOp::NoArg, Instr.I32Xor, Instr.I32Xor::class) + opMapEntry("i32.shl", NumOp::NoArg, Instr.I32Shl, Instr.I32Shl::class) + opMapEntry("i32.shr_s", NumOp::NoArg, Instr.I32ShrS, Instr.I32ShrS::class) + opMapEntry("i32.shr_u", NumOp::NoArg, Instr.I32ShrU, Instr.I32ShrU::class) + opMapEntry("i32.rotl", NumOp::NoArg, Instr.I32Rotl, Instr.I32Rotl::class) + opMapEntry("i32.rotr", NumOp::NoArg, Instr.I32Rotr, Instr.I32Rotr::class) + opMapEntry("i64.clz", NumOp::NoArg, Instr.I64Clz, Instr.I64Clz::class) + opMapEntry("i64.ctz", NumOp::NoArg, Instr.I64Ctz, Instr.I64Ctz::class) + opMapEntry("i64.popcnt", NumOp::NoArg, Instr.I64Popcnt, Instr.I64Popcnt::class) + opMapEntry("i64.add", NumOp::NoArg, Instr.I64Add, Instr.I64Add::class) + opMapEntry("i64.sub", NumOp::NoArg, Instr.I64Sub, Instr.I64Sub::class) + opMapEntry("i64.mul", NumOp::NoArg, Instr.I64Mul, Instr.I64Mul::class) + opMapEntry("i64.div_s", NumOp::NoArg, Instr.I64DivS, Instr.I64DivS::class) + opMapEntry("i64.div_u", NumOp::NoArg, Instr.I64DivU, Instr.I64DivU::class) + opMapEntry("i64.rem_s", NumOp::NoArg, Instr.I64RemS, Instr.I64RemS::class) + opMapEntry("i64.rem_u", NumOp::NoArg, Instr.I64RemU, Instr.I64RemU::class) + opMapEntry("i64.and", NumOp::NoArg, Instr.I64And, Instr.I64And::class) + opMapEntry("i64.or", NumOp::NoArg, Instr.I64Or, Instr.I64Or::class) + opMapEntry("i64.xor", NumOp::NoArg, Instr.I64Xor, Instr.I64Xor::class) + opMapEntry("i64.shl", NumOp::NoArg, Instr.I64Shl, Instr.I64Shl::class) + opMapEntry("i64.shr_s", NumOp::NoArg, Instr.I64ShrS, Instr.I64ShrS::class) + opMapEntry("i64.shr_u", NumOp::NoArg, Instr.I64ShrU, Instr.I64ShrU::class) + opMapEntry("i64.rotl", NumOp::NoArg, Instr.I64Rotl, Instr.I64Rotl::class) + opMapEntry("i64.rotr", NumOp::NoArg, Instr.I64Rotr, Instr.I64Rotr::class) + opMapEntry("f32.abs", NumOp::NoArg, Instr.F32Abs, Instr.F32Abs::class) + opMapEntry("f32.neg", NumOp::NoArg, Instr.F32Neg, Instr.F32Neg::class) + opMapEntry("f32.ceil", NumOp::NoArg, Instr.F32Ceil, Instr.F32Ceil::class) + opMapEntry("f32.floor", NumOp::NoArg, Instr.F32Floor, Instr.F32Floor::class) + opMapEntry("f32.trunc", NumOp::NoArg, Instr.F32Trunc, Instr.F32Trunc::class) + opMapEntry("f32.nearest", NumOp::NoArg, Instr.F32Nearest, Instr.F32Nearest::class) + opMapEntry("f32.sqrt", NumOp::NoArg, Instr.F32Sqrt, Instr.F32Sqrt::class) + opMapEntry("f32.add", NumOp::NoArg, Instr.F32Add, Instr.F32Add::class) + opMapEntry("f32.sub", NumOp::NoArg, Instr.F32Sub, Instr.F32Sub::class) + opMapEntry("f32.mul", NumOp::NoArg, Instr.F32Mul, Instr.F32Mul::class) + opMapEntry("f32.div", NumOp::NoArg, Instr.F32Div, Instr.F32Div::class) + opMapEntry("f32.min", NumOp::NoArg, Instr.F32Min, Instr.F32Min::class) + opMapEntry("f32.max", NumOp::NoArg, Instr.F32Max, Instr.F32Max::class) + opMapEntry("f32.copysign", NumOp::NoArg, Instr.F32CopySign, Instr.F32CopySign::class) + opMapEntry("f64.abs", NumOp::NoArg, Instr.F64Abs, Instr.F64Abs::class) + opMapEntry("f64.neg", NumOp::NoArg, Instr.F64Neg, Instr.F64Neg::class) + opMapEntry("f64.ceil", NumOp::NoArg, Instr.F64Ceil, Instr.F64Ceil::class) + opMapEntry("f64.floor", NumOp::NoArg, Instr.F64Floor, Instr.F64Floor::class) + opMapEntry("f64.trunc", NumOp::NoArg, Instr.F64Trunc, Instr.F64Trunc::class) + opMapEntry("f64.nearest", NumOp::NoArg, Instr.F64Nearest, Instr.F64Nearest::class) + opMapEntry("f64.sqrt", NumOp::NoArg, Instr.F64Sqrt, Instr.F64Sqrt::class) + opMapEntry("f64.add", NumOp::NoArg, Instr.F64Add, Instr.F64Add::class) + opMapEntry("f64.sub", NumOp::NoArg, Instr.F64Sub, Instr.F64Sub::class) + opMapEntry("f64.mul", NumOp::NoArg, Instr.F64Mul, Instr.F64Mul::class) + opMapEntry("f64.div", NumOp::NoArg, Instr.F64Div, Instr.F64Div::class) + opMapEntry("f64.min", NumOp::NoArg, Instr.F64Min, Instr.F64Min::class) + opMapEntry("f64.max", NumOp::NoArg, Instr.F64Max, Instr.F64Max::class) + opMapEntry("f64.copysign", NumOp::NoArg, Instr.F64CopySign, Instr.F64CopySign::class) + + opMapEntry("i32.wrap/i64", ConvertOp::NoArg, Instr.I32WrapI64, Instr.I32WrapI64::class) + opMapEntry("i32.trunc_s/f32", ConvertOp::NoArg, Instr.I32TruncSF32, Instr.I32TruncSF32::class) + opMapEntry("i32.trunc_u/f32", ConvertOp::NoArg, Instr.I32TruncUF32, Instr.I32TruncUF32::class) + opMapEntry("i32.trunc_s/f64", ConvertOp::NoArg, Instr.I32TruncSF64, Instr.I32TruncSF64::class) + opMapEntry("i32.trunc_u/f64", ConvertOp::NoArg, Instr.I32TruncUF64, Instr.I32TruncUF64::class) + opMapEntry("i64.extend_s/i32", ConvertOp::NoArg, Instr.I64ExtendSI32, Instr.I64ExtendSI32::class) + opMapEntry("i64.extend_u/i32", ConvertOp::NoArg, Instr.I64ExtendUI32, Instr.I64ExtendUI32::class) + opMapEntry("i64.trunc_s/f32", ConvertOp::NoArg, Instr.I64TruncSF32, Instr.I64TruncSF32::class) + opMapEntry("i64.trunc_u/f32", ConvertOp::NoArg, Instr.I64TruncUF32, Instr.I64TruncUF32::class) + opMapEntry("i64.trunc_s/f64", ConvertOp::NoArg, Instr.I64TruncSF64, Instr.I64TruncSF64::class) + opMapEntry("i64.trunc_u/f64", ConvertOp::NoArg, Instr.I64TruncUF64, Instr.I64TruncUF64::class) + opMapEntry("f32.convert_s/i32", ConvertOp::NoArg, Instr.F32ConvertSI32, Instr.F32ConvertSI32::class) + opMapEntry("f32.convert_u/i32", ConvertOp::NoArg, Instr.F32ConvertUI32, Instr.F32ConvertUI32::class) + opMapEntry("f32.convert_s/i64", ConvertOp::NoArg, Instr.F32ConvertSI64, Instr.F32ConvertSI64::class) + opMapEntry("f32.convert_u/i64", ConvertOp::NoArg, Instr.F32ConvertUI64, Instr.F32ConvertUI64::class) + opMapEntry("f32.demote/f64", ConvertOp::NoArg, Instr.F32DemoteF64, Instr.F32DemoteF64::class) + opMapEntry("f64.convert_s/i32", ConvertOp::NoArg, Instr.F64ConvertSI32, Instr.F64ConvertSI32::class) + opMapEntry("f64.convert_u/i32", ConvertOp::NoArg, Instr.F64ConvertUI32, Instr.F64ConvertUI32::class) + opMapEntry("f64.convert_s/i64", ConvertOp::NoArg, Instr.F64ConvertSI64, Instr.F64ConvertSI64::class) + opMapEntry("f64.convert_u/i64", ConvertOp::NoArg, Instr.F64ConvertUI64, Instr.F64ConvertUI64::class) + opMapEntry("f64.promote/f32", ConvertOp::NoArg, Instr.F64PromoteF32, Instr.F64PromoteF32::class) + + opMapEntry("i32.reinterpret/f32", ReinterpretOp::NoArg, Instr.I32ReinterpretF32, Instr.I32ReinterpretF32::class) + opMapEntry("i64.reinterpret/f64", ReinterpretOp::NoArg, Instr.I64ReinterpretF64, Instr.I64ReinterpretF64::class) + opMapEntry("f32.reinterpret/i32", ReinterpretOp::NoArg, Instr.F32ReinterpretI32, Instr.F32ReinterpretI32::class) + opMapEntry("f64.reinterpret/i64", ReinterpretOp::NoArg, Instr.F64ReinterpretI64, Instr.F64ReinterpretI64::class) + } + } + } +} diff --git a/src/main/kotlin/asmble/ast/SExpr.kt b/src/main/kotlin/asmble/ast/SExpr.kt index 85ef1a9..200d641 100644 --- a/src/main/kotlin/asmble/ast/SExpr.kt +++ b/src/main/kotlin/asmble/ast/SExpr.kt @@ -1,72 +1,6 @@ package asmble.ast -import asmble.util.Either - sealed class SExpr { data class Multi(val vals: List = emptyList()) : SExpr() data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() - - companion object { - data class ParseError(val charOffset: Int, val msg: String) - fun parse(str: CharSequence): Either { - val state = ParseState(str) - var ret = emptyList() - while (!state.isEof) { - ret += state.nextSExpr() - if (state.err != null) return Either.Right(ParseError(state.offset, state.err!!)) - } - if (ret.size == 1 && ret[0] is Multi) return Either.Left(ret[0] as Multi) - else return Either.Left(Multi(ret)) - } - - private class ParseState(val str: CharSequence, var offset: Int = 0, var err: String? = null) { - fun nextSExpr(): SExpr { - skipWhitespace() - if (isEof) return Multi() - // What type of expr do we have here? - when (str[offset]) { - '(' -> { - offset++ - var ret = Multi() - while (err == null && !isEof && str[offset] != ')') { - ret = ret.copy(ret.vals + nextSExpr()) - } - if (err == null) { - if (str[offset] == ')') offset++ else err = "EOF when expected ')'" - } - return ret - } - '"' -> { - offset++ - // Anything can be escaped (for now) - var retStr = "" - while (!isEof && str[offset] != '"') { - if (str[offset] == '\\') { - ++offset - if (isEof) { - err = "EOF when expected char to unescape" - break - } - } - retStr += str[offset] - offset++ - } - if (err == null && str[offset] != '"') err = "EOF when expected '\"'" - return Symbol(retStr, true) - } - else -> { - // Go until next quote or whitespace or parens - val origOffset = offset - while (!isEof && str[offset] != '(' && str[offset] != ')' && - str[offset] != '"' && !str[offset].isWhitespace()) offset++ - return Symbol(str.substring(origOffset, offset)) - } - } - } - - fun skipWhitespace() { while (!isEof && str[offset].isWhitespace()) offset++ } - - val isEof: Boolean inline get() = offset >= str.length - } - } } \ No newline at end of file diff --git a/src/main/kotlin/asmble/io/SExprToAst.kt b/src/main/kotlin/asmble/io/SExprToAst.kt new file mode 100644 index 0000000..5564d6b --- /dev/null +++ b/src/main/kotlin/asmble/io/SExprToAst.kt @@ -0,0 +1,394 @@ +package asmble.io + +import asmble.ast.Node +import asmble.ast.Node.InstrOp +import asmble.ast.SExpr +import asmble.util.takeUntilNull + +class SExprToAst { + fun toBlockSigMaybe(exp: SExpr.Multi, offset: Int): List { + val types = exp.vals.drop(offset).asSequence().map { + if (it is SExpr.Symbol) toTypeMaybe(it) else null + }.takeUntilNull().toList() + // We can only handle one type for now + require(types.size <= 1) + return types + } + + fun toElemType(exp: SExpr.Multi, offset: Int): Node.ElemType { + exp.vals[offset].requireSymbol("anyfunc") + return Node.ElemType.ANYFUNC + } + + fun toExport(exp: SExpr.Multi): Node.Export { + exp.vals.first().requireSymbol("export") + val field = (exp.vals[1] as SExpr.Symbol).contents + val kind = exp.vals[2] as SExpr.Multi + val kindIndex = toVar(kind.vals[1] as SExpr.Symbol) + val extKind = when((kind.vals[0] as SExpr.Symbol).contents) { + "func" -> Node.ExternalKind.FUNCTION + "global" -> Node.ExternalKind.GLOBAL + "table" -> Node.ExternalKind.TABLE + "memory" -> Node.ExternalKind.MEMORY + else -> throw Exception("Unrecognized kind: ${kind.vals[0]}") + } + return Node.Export(field, extKind, kindIndex) + } + + fun toExprMaybe(exp: SExpr.Multi): List { + // or + + val maybeOpAndOffset = toOpMaybe(exp, 0) + if (maybeOpAndOffset != null) { + // Everything left in the multi should be a a multi expression + return exp.vals.drop(maybeOpAndOffset.second).flatMap { + toExprMaybe(it as SExpr.Multi) + } + maybeOpAndOffset.first + } + // Other blocks take up the rest (ignore names) + val blockName = (exp.vals.first() as SExpr.Symbol).contents + var opOffset = 1 + if (exp.maybeName(opOffset) != null) opOffset++ + val sigs = toBlockSigMaybe(exp, opOffset) + opOffset += sigs.size + when(blockName) { + "block" -> + return listOf(Node.Instr.Block(sigs.firstOrNull())) + + toInstrs(exp, opOffset).first + Node.Instr.End + "loop" -> + return listOf(Node.Instr.Loop(sigs.firstOrNull())) + + toInstrs(exp, opOffset).first + Node.Instr.End + "if" -> { + if (opOffset >= exp.vals.size) return emptyList() + var ret = emptyList() + // Try expressions + var exprMulti = exp.vals[opOffset] as SExpr.Multi + var exprs = toExprMaybe(exprMulti) + // Conditional? + if (exprs.isNotEmpty()) { + // First expression means it's the conditional, so push on stack + ret += exprs + opOffset++ + exprMulti = exp.vals[opOffset] as SExpr.Multi + } + ret += Node.Instr.If(sigs.firstOrNull()) + // Is it a "then"? + if (exprMulti.vals[0] is SExpr.Symbol && (exprMulti.vals[0] as SExpr.Symbol).contents == "then") { + ret += toInstrs(exprMulti, 1).first + } else ret += toExprMaybe(exprMulti) + // Now check for "else" + opOffset++ + if (opOffset < exp.vals.size) { + ret += Node.Instr.Else + exprMulti = exp.vals[opOffset] as SExpr.Multi + if (exprMulti.vals[0] is SExpr.Symbol && (exprMulti.vals[0] as SExpr.Symbol).contents == "else") { + ret += toInstrs(exprMulti, 1).first + } else ret += toExprMaybe(exprMulti) + } + return ret + Node.Instr.End + } + else -> return emptyList() + } + } + + fun toFunc(exp: SExpr.Multi): Pair { + exp.vals.first().requireSymbol("func") + // TODO: export/import + var currentIndex = 1 + val name = exp.maybeName(currentIndex) + if (name != null) currentIndex++ + val sig = toFuncSig(exp.vals[currentIndex] as SExpr.Multi) + currentIndex++ + val locals = exp.repeated("local", currentIndex, { toLocals(it).second }).flatten() + currentIndex += locals.size + val (instrs, _) = toInstrs(exp, currentIndex) + return Pair(name, Node.Func(sig, locals, instrs)) + } + + fun toFuncSig(exp: SExpr.Multi): Node.Type.Func { + // TODO: type form? + val params = exp.repeated("param", 0, { toParams(it).second }).flatten() + val results = exp.vals.drop(params.size).map { toResult(it as SExpr.Multi) } + require(results.size <= 1) + return Node.Type.Func(params, results.firstOrNull()) + } + + fun toGlobalSig(exp: SExpr): Node.Type.Global = when(exp) { + is SExpr.Symbol -> Node.Type.Global(toType(exp), false) + is SExpr.Multi -> { + exp.vals.first().requireSymbol("mut") + Node.Type.Global(toType(exp.vals[1] as SExpr.Symbol), true) + } + } + + fun toImport(exp: SExpr.Multi): Triple { + exp.vals.first().requireSymbol("import") + val module = (exp.vals[1] as SExpr.Symbol).contents + val field = (exp.vals[2] as SExpr.Symbol).contents + val kind = exp.vals[3] as SExpr.Multi + val kindName = (kind.vals[0] as SExpr.Symbol).contents + val kindSubOffset = if (kind.maybeName(1) == null) 1 else 2 + return Triple(module, field, when(kindName) { + "func" -> toFuncSig(kind.vals[kindSubOffset] as SExpr.Multi) + "global" -> toGlobalSig(kind.vals[kindSubOffset]) + "table" -> toTableSig(kind, kindSubOffset) + "memory" -> toMemorySig(kind, kindSubOffset) + else -> throw Exception("Unrecognized type: $kindName") + }) + } + + fun toInstrs(exp: SExpr.Multi, offset: Int): Pair, Int> { + var runningOffset = 0 + var ret = emptyList() + while (offset + runningOffset < exp.vals.size) { + val maybeInstrAndOffset = toInstrMaybe(exp, offset + runningOffset) + if (maybeInstrAndOffset.first.isEmpty()) break + ret += maybeInstrAndOffset.first + runningOffset += maybeInstrAndOffset.second + } + return Pair(ret, runningOffset) + } + + fun toInstrMaybe(exp: SExpr.Multi, offset: Int): Pair, Int> { + // + if (exp.vals[offset] is SExpr.Multi) { + val exprs = toExprMaybe(exp) + return Pair(exprs, if (exprs.isEmpty()) 0 else 1) + } + // + val maybeOpAndOffset = toOpMaybe(exp, offset) + if (maybeOpAndOffset != null) { + return Pair(listOf(maybeOpAndOffset.first), maybeOpAndOffset.second) + } + // Other blocks (ignore names) + if (exp.vals[offset] !is SExpr.Symbol) return Pair(emptyList(), 0) + val blockName = (exp.vals[offset] as SExpr.Symbol).contents + var opOffset = 1 + if (exp.maybeName(offset + opOffset) != null) opOffset++ + val sigs = toBlockSigMaybe(exp, offset + opOffset) + opOffset += sigs.size + var ret = emptyList() + when(blockName) { + "block" -> { + ret += Node.Instr.Block(sigs.firstOrNull()) + toInstrs(exp, offset + opOffset).also { + ret += it.first + opOffset += it.second + } + } + "loop" -> { + ret += Node.Instr.Loop(sigs.firstOrNull()) + toInstrs(exp, offset + opOffset).also { + ret += it.first + opOffset += it.second + } + } + "if" -> { + ret += Node.Instr.Loop(sigs.firstOrNull()) + toInstrs(exp, offset + opOffset).also { + ret += it.first + opOffset += it.second + } + // Else? + if (offset + opOffset < exp.vals.size) { + if ((exp.vals[offset + opOffset] as? SExpr.Symbol)?.contents == "else") { + ret += Node.Instr.Else + opOffset++ + if (exp.maybeName(offset + opOffset) != null) opOffset++ + toInstrs(exp, offset + opOffset).also { + ret += it.first + opOffset += it.second + } + } + } + } + else -> return Pair(emptyList(), 0) + } + require((exp.vals[offset + opOffset] as? SExpr.Symbol)?.contents == "end") + opOffset++ + if (exp.maybeName(offset + opOffset) != null) opOffset++ + return Pair(ret, opOffset) + } + + fun toLocals(exp: SExpr.Multi): Pair> { + exp.vals.first().requireSymbol("local") + val name = exp.maybeName(1) + if (name != null) return Pair(name, listOf(toType(exp.vals[2] as SExpr.Symbol))) + return Pair(null, exp.vals.drop(1).map { toType(it as SExpr.Symbol) }) + } + + fun toMemorySig(exp: SExpr.Multi, offset: Int): Node.Type.Memory { + return Node.Type.Memory(toResizeableLimits(exp, offset)) + } + + fun toModule(exp: SExpr.Multi): Pair { + exp.vals.first().requireSymbol("module") + var currIndex = 1 + val name = exp.maybeName(currIndex) + if (name != null) currIndex++ + var types = exp.repeated("type", currIndex, { toTypeDef(it).second }) + currIndex += types.size + val funcs = exp.repeated("func", currIndex, { toFunc(it).second }) + currIndex += funcs.size + val imports = exp.repeated("import", currIndex, this::toImport).map { + when(it.third) { + is Node.Type.Func -> { + // TODO: why does smart cast not work here? + types += it.third as Node.Type.Func + Node.Import(it.first, it.second, Node.Import.Kind.Func(types.lastIndex)) + } + is Node.Type.Table -> + Node.Import(it.first, it.second, Node.Import.Kind.Table(it.third as Node.Type.Table)) + is Node.Type.Memory -> + Node.Import(it.first, it.second, Node.Import.Kind.Memory(it.third as Node.Type.Memory)) + is Node.Type.Global -> + Node.Import(it.first, it.second, Node.Import.Kind.Global(it.third as Node.Type.Global)) + else -> + throw Exception("Unrecognized import kind: ${it.third}") + } + } + currIndex += imports.size + val exports = exp.repeated("export", currIndex, this::toExport) + currIndex += exports.size + } + + fun toOpMaybe(exp: SExpr.Multi, offset: Int): Pair? { + if (offset >= exp.vals.size) return null + val head = exp.vals[offset] as SExpr.Symbol + fun oneVar() = toVar(exp.vals[offset + 1] as SExpr.Symbol) + val op = InstrOp.strToOpMap[head.contents] + return when(op) { + null -> null + is InstrOp.ControlFlowOp.NoArg -> Pair(op.create, 1) + is InstrOp.ControlFlowOp.TypeArg -> return null // Type not handled here + is InstrOp.ControlFlowOp.DepthArg -> Pair(op.create(oneVar()), 2) + is InstrOp.ControlFlowOp.TableArg -> { + val vars = exp.vals.drop(offset + 1).asSequence().map(this::toVarMaybe).takeUntilNull().toList() + Pair(op.create(vars.drop(1), vars.first()), offset + vars.size) + } + is InstrOp.CallOp.IndexArg -> Pair(op.create(oneVar()), 2) + is InstrOp.CallOp.IndexReservedArg -> Pair(op.create(oneVar(), false), 2) + is InstrOp.ParamOp.NoArg -> Pair(op.create, 1) + is InstrOp.VarOp.IndexArg -> Pair(op.create(oneVar()), 2) + is InstrOp.MemOp.FlagsOffsetArg -> { + var count = 1 + var instrOffset = 0 + var instrAlign = 0 + if (exp.vals.size > offset + count) { + val maybeSym = exp.vals[offset + count] as? SExpr.Symbol + if (maybeSym != null && maybeSym.contents.startsWith("offset=")) { + instrOffset = maybeSym.contents.substring(7).toInt() + count++ + } + } + if (exp.vals.size > offset + count) { + val maybeSym = exp.vals[offset + count] as? SExpr.Symbol + if (maybeSym != null && maybeSym.contents.startsWith("align=")) { + instrAlign = maybeSym.contents.substring(6).toInt() + count++ + } + } + Pair(op.create(instrAlign, instrOffset), count) + } + is InstrOp.MemOp.ReservedArg -> Pair(op.create(false), 1) + is InstrOp.ConstOp.IntArg -> Pair(op.create((exp.vals[offset + 1] as SExpr.Symbol).contents.toInt()), 2) + is InstrOp.ConstOp.LongArg -> Pair(op.create((exp.vals[offset + 1] as SExpr.Symbol).contents.toLong()), 2) + is InstrOp.ConstOp.FloatArg -> Pair(op.create((exp.vals[offset + 1] as SExpr.Symbol).contents.toFloat()), 2) + is InstrOp.ConstOp.DoubleArg -> Pair(op.create((exp.vals[offset + 1] as SExpr.Symbol).contents.toDouble()), 2) + is InstrOp.CompareOp.NoArg -> Pair(op.create, 1) + is InstrOp.NumOp.NoArg -> Pair(op.create, 1) + is InstrOp.ConvertOp.NoArg -> Pair(op.create, 1) + is InstrOp.ReinterpretOp.NoArg -> Pair(op.create, 1) + } + } + + fun toParams(exp: SExpr.Multi): Pair> { + exp.vals.first().requireSymbol("param") + val name = exp.maybeName(1) + if (name != null) return Pair(name, listOf(toType(exp.vals[2] as SExpr.Symbol))) + return Pair(null, exp.vals.drop(1).map { toType(it as SExpr.Symbol) }) + } + + fun toResizeableLimits(exp: SExpr.Multi, offset: Int): Node.ResizableLimits { + var max: Int? = null + if (offset + 1 < exp.vals.size && exp.vals[offset + 1] is SExpr.Symbol) { + max = (exp.vals[offset + 1] as SExpr.Symbol).contents.toIntOrNull() + } + return Node.ResizableLimits((exp.vals[offset] as SExpr.Symbol).contents.toInt(), max) + } + + fun toResult(exp: SExpr.Multi): Node.Type.Value { + exp.vals.first().requireSymbol("result") + return toType(exp.vals[1] as SExpr.Symbol) + } + + fun toTableSig(exp: SExpr.Multi, offset: Int): Node.Type.Table { + val limits = toResizeableLimits(exp, offset) + return Node.Type.Table(toElemType(exp, offset + if (limits.maximum == null) 0 else 1), limits) + } + + fun toType(exp: SExpr.Symbol): Node.Type.Value { + return toTypeMaybe(exp) ?: throw Exception("Unknown value type: ${exp.contents}") + } + + fun toTypeMaybe(exp: SExpr.Symbol): Node.Type.Value? = when(exp.contents) { + "i32" -> Node.Type.Value.I32 + "i64" -> Node.Type.Value.I64 + "f32" -> Node.Type.Value.F32 + "f64" -> Node.Type.Value.F64 + else -> null + } + + fun toTypeDef(exp: SExpr.Multi): Pair { + exp.vals.first().requireSymbol("typedef") + var currIndex = 1 + val name = exp.maybeName(currIndex) + if (name != null) currIndex++ + val funcSigExp = exp.vals[currIndex] as SExpr.Multi + funcSigExp.vals[0].requireSymbol("func") + return Pair(name, toFuncSig(funcSigExp.vals[1] as SExpr.Multi)) + } + + fun toVar(exp: SExpr.Symbol): Int { + // TODO: what about name? + return exp.contents.toInt() + } + + fun toVarMaybe(exp: SExpr): Int? { + // TODO: what about name? + if (exp !is SExpr.Symbol) return null + return exp.contents.toIntOrNull() + } + + private fun SExpr.requireSymbol(contents: String, quotedCheck: Boolean? = null) { + if (this is SExpr.Symbol && this.contents == contents && + (quotedCheck == null || this.quoted == quotedCheck)) { + return + } + throw Exception("Expected symbol of $contents, got $this") + } + + private fun SExpr.Multi.maybeName(index: Int): String? { + if (this.vals.size > index && this.vals[index] is SExpr.Symbol) { + val sym = this.vals[index] as SExpr.Symbol + if (!sym.quoted && sym.contents[0] == '$') return sym.contents + } + return null + } + + private fun SExpr.Multi.repeated(name: String, startOffset: Int, fn: (SExpr.Multi) -> T): List { + var offset = startOffset + var ret = emptyList() + while (this.vals.size > offset) { + if (this.vals[offset] !is SExpr.Multi) break + val expMulti = this.vals[offset] as SExpr.Multi + if (expMulti.vals[0] !is SExpr.Symbol) break + val expName = expMulti.vals[0] as SExpr.Symbol + if (expName.quoted || expName.contents != name) break + ret += fn(expMulti) + offset++ + } + return ret + } +} + + diff --git a/src/main/kotlin/asmble/io/StrToSExpr.kt b/src/main/kotlin/asmble/io/StrToSExpr.kt new file mode 100644 index 0000000..04ae7c2 --- /dev/null +++ b/src/main/kotlin/asmble/io/StrToSExpr.kt @@ -0,0 +1,87 @@ +package asmble.io + +import asmble.ast.SExpr +import asmble.util.Either + +open class StrToSExpr { + data class ParseError(val charOffset: Int, val msg: String) + + fun parse(str: CharSequence): Either { + val state = ParseState(str) + var ret = emptyList() + while (!state.isEof) { + ret += state.nextSExpr() + if (state.err != null) return Either.Right(ParseError(state.offset, state.err!!)) + } + if (ret.size == 1 && ret[0] is SExpr.Multi) return Either.Left(ret[0] as SExpr.Multi) + else return Either.Left(SExpr.Multi(ret)) + } + + private class ParseState(val str: CharSequence, var offset: Int = 0, var err: String? = null) { + fun nextSExpr(): SExpr { + skipWhitespace() + if (isEof) return SExpr.Multi() + // What type of expr do we have here? + when (str[offset]) { + '(' -> { + offset++ + var ret = SExpr.Multi() + while (err == null && !isEof && str[offset] != ')') { + ret = ret.copy(ret.vals + nextSExpr()) + } + if (err == null) { + if (str[offset] == ')') offset++ else err = "EOF when expected ')'" + } + return ret + } + '"' -> { + offset++ + // Check escapes + var retStr = "" + while (err == null && !isEof && str[offset] != '"') { + if (str[offset] == '\\') { + offset++ + if (isEof) err = "EOF when expected char to unescape" else when (str[offset]) { + 'n' -> retStr += '\n' + 't' -> retStr += '\t' + '\\' -> retStr += '\\' + '\'' -> retStr += '\'' + '"' -> retStr += '"' + else -> { + // Try to parse hex if there is enough, otherwise just gripe + if (offset + 4 >= str.length) err = "Unknown escape" else { + try { + retStr += str.substring(offset, offset + 4).toByte(16).toChar() + offset += 4 + } catch (e: NumberFormatException) { + err = "Unknown escape" + } + } + } + } + } else { + retStr += str[offset] + offset++ + } + } + if (err == null && str[offset] != '"') err = "EOF when expected '\"'" + else if (err == null) offset++ + return SExpr.Symbol(retStr, true) + } + else -> { + // Go until next quote or whitespace or parens + val origOffset = offset + while (!isEof && str[offset] != '(' && str[offset] != ')' && + str[offset] != '"' && !str[offset].isWhitespace()) offset++ + return SExpr.Symbol(str.substring(origOffset, offset)) + } + } + } + + fun skipWhitespace() { while (!isEof && str[offset].isWhitespace()) offset++ } + + val isEof: Boolean inline get() = offset >= str.length + } + + companion object : StrToSExpr() +} \ No newline at end of file diff --git a/src/main/kotlin/asmble/util/CollectionExt.kt b/src/main/kotlin/asmble/util/CollectionExt.kt new file mode 100644 index 0000000..edecf09 --- /dev/null +++ b/src/main/kotlin/asmble/util/CollectionExt.kt @@ -0,0 +1,7 @@ +package asmble.util + +fun Collection.takeUntilNull(): Collection { + return this.asSequence().takeUntilNull().toList() +} + + diff --git a/src/main/kotlin/asmble/util/SequenceExt.kt b/src/main/kotlin/asmble/util/SequenceExt.kt new file mode 100644 index 0000000..7ae07c5 --- /dev/null +++ b/src/main/kotlin/asmble/util/SequenceExt.kt @@ -0,0 +1,7 @@ +package asmble.util + +fun Sequence.takeUntilNull(): Sequence { + // Unchecked cast, oh well, it's erased + return this.takeWhile({ it != null }) as Sequence +} + diff --git a/src/test/resources/spec b/src/test/resources/spec new file mode 160000 index 0000000..0b9b987 --- /dev/null +++ b/src/test/resources/spec @@ -0,0 +1 @@ +Subproject commit 0b9b9878619aec23980fe713b3a7abf5a991e152