use super::{CodecTarget, Constant, Handle, Handoff, Routine, Scalar};
use crate::adapter::lua::declared_place;
use crate::encode::EncodeError;
use zerodds_ast::{Ast, CharWidth, MemberNode, TypeKind, TypeNode};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal};
use zerodds_lang_cst::lua::{self, Expr, Place, Stat};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lua;
pub const CODECS: &str = "ZD";
pub const RUNTIME: &str = "zdw";
const MAX_TYPE_DEPTH: usize = 64;
const SIGNED_CHAR: &str = "signed char";
const MAP_ENTRY: &str = "_zd_map_entry";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LuaType(pub String);
impl Lua {
fn place_from_name(text: &str) -> Place {
let mut segments = text.split('.');
let mut place = Place::name(segments.next().unwrap_or(text));
for segment in segments {
place = place.field(segment);
}
place
}
fn place_of(value: Expr) -> Place {
match value {
Expr::Place(place) => place,
Expr::Paren(inner) => match *inner {
Expr::Place(place) => place,
other => Place::Paren(Box::new(other)),
},
other => Place::Paren(Box::new(other)),
}
}
fn runtime_call(method: &str, args: Vec<Expr>) -> Expr {
Expr::Call {
func: Box::new(Place::name(RUNTIME).field(method)),
args,
}
}
fn codec_call(callee: &str, args: Vec<Expr>) -> Expr {
Expr::Call {
func: Box::new(Place::name(CODECS).field(callee)),
args,
}
}
fn text(value: &str) -> Expr {
Expr::string(value)
}
fn reading_body(name: &str, body: Vec<Stat>) -> Vec<Stat> {
let defaulter = crate::default_lua::defaulter_from_decoder(name);
let mut statements = vec![Stat::Local {
doc: Doc::none(),
name: Ident::new("v"),
value: Some(Self::codec_call(&defaulter, Vec::new())),
}];
statements.extend(body);
statements.push(Stat::Return(Some(Expr::place(Place::name("v")))));
statements
}
}
impl CodecTarget for Lua {
type Expr = Expr;
type Stmt = Stat;
type Block = Vec<Stat>;
type Type = LuaType;
type Decl = Stat;
fn scalar(kind: Scalar) -> Self::Type {
LuaType(
match kind {
Scalar::Bool => "boolean",
Scalar::Char => "integer",
Scalar::Int8
| Scalar::UInt8
| Scalar::Int16
| Scalar::UInt16
| Scalar::Int32
| Scalar::UInt32
| Scalar::Int64
| Scalar::UInt64
| Scalar::Index
| Scalar::Size => "integer",
Scalar::Float | Scalar::Double => "number",
Scalar::LongDouble => "string",
}
.to_string(),
)
}
fn named(name: &str) -> Self::Type {
LuaType(name.to_string())
}
fn nothing() -> Self::Type {
LuaType("nil".to_string())
}
fn handle(_kind: Handle) -> Self::Type {
LuaType("table".to_string())
}
fn code_point(value: Self::Expr, _narrow: bool) -> Self::Expr {
value
}
fn constant(which: Constant) -> Self::Expr {
match which {
Constant::WcharWidth2 => Expr::int(2),
Constant::WcharWidth4 => Expr::int(4),
Constant::WcharFixed => Self::text("fixed"),
Constant::WcharGiopLengthPrefixed => Self::text("giop-length-prefixed"),
Constant::WstringUnitsStream => Self::text("stream"),
Constant::WstringUnitsBigEndianAlways => Self::text("big-endian-always"),
Constant::WstringUnitsByteOrderMark => Self::text("byte-order-mark"),
Constant::WstringUnitsHostUnswapped => Self::text("host-unswapped"),
Constant::WstringSpecification => Self::text("specification"),
Constant::WstringCharacterCount => Self::text("character-count"),
Constant::WstringCharacterCountTerminated => Self::text("character-count-terminated"),
Constant::WstringSpecificationWideUnits => Self::text("specification-wide-units"),
Constant::WstringRosTypeSupport => Self::text("ros-type-support"),
Constant::WstringOctetsTerminated2 => Self::text("octets/terminated/2"),
Constant::WstringOctetsTerminated4 => Self::text("octets/terminated/4"),
Constant::WstringCharactersTerminated2 => Self::text("characters/terminated/2"),
Constant::FixedDeclared => Self::text("declared"),
Constant::FixedValuePrecision => Self::text("value-precision"),
Constant::FixedValueStringDigits => Self::text("value-string-digits"),
Constant::ParameterMember => Self::text("member"),
Constant::ParameterOptional => Self::text("optional"),
Constant::TryConstructDiscard => Self::text("discard"),
Constant::TryConstructTrim => Self::text("trim"),
Constant::TryConstructUseDefault => Self::text("use-default"),
Constant::StatusOk => Self::text("ok"),
Constant::StatusDiscarded => Self::text("discarded"),
Constant::StatusImpossibleLength => Self::text("impossible-length"),
Constant::StatusOutOfRange => Self::text("out-of-range"),
Constant::StatusTruncated => Self::text("truncated"),
}
}
fn unsigned_holder(_bits: u16) -> Self::Type {
LuaType("integer".to_string())
}
fn unsigned_less(octets: u8, left: Self::Expr, right: Self::Expr) -> Self::Expr {
if octets < 8 {
return Self::binary("<", left, right);
}
Expr::Call {
func: Box::new(Place::name("math").field("ult")),
args: vec![left, right],
}
}
fn bool_less(left: Self::Expr, right: Self::Expr) -> Self::Expr {
Expr::Binary {
op: "and",
lhs: Box::new(Expr::Unary {
op: "not",
value: Box::new(Expr::Paren(Box::new(left))),
}),
rhs: Box::new(Expr::Paren(Box::new(right))),
}
}
fn value_type(ast: &Ast, id: Id<TypeNode>) -> Self::Type {
value_type_of(ast, id, 0)
}
fn type_name(ast: &Ast, id: Id<TypeNode>) -> String {
type_tag(ast, id, 0)
}
fn map_entry_name(_ast: &Ast, _id: Id<TypeNode>) -> String {
MAP_ENTRY.to_string()
}
fn module_prefix(_ast: &Ast, _id: Id<TypeNode>) -> String {
String::new()
}
fn escape(name: &str) -> String {
name.to_string()
}
fn name(text: &str) -> Self::Expr {
Expr::place(Self::place_from_name(text))
}
fn int(value: i128) -> Self::Expr {
Expr::int(value)
}
fn wide_int(value: i128) -> Self::Expr {
if value == i128::from(i64::MIN) {
return Expr::Literal(Literal::Int("0x8000000000000000".to_string()));
}
if i64::try_from(value).is_ok() {
return Expr::int(value);
}
if let Ok(unsigned) = u64::try_from(value) {
return Expr::Literal(Literal::Int(format!("0x{unsigned:016X}")));
}
#[allow(clippy::cast_sign_loss)]
let masked = (value as u64).wrapping_add(0);
Expr::Literal(Literal::Int(format!("0x{masked:016X}")))
}
fn wide_unsigned(value: u64) -> Self::Expr {
if i64::try_from(value).is_ok() {
return Expr::int(i128::from(value));
}
Expr::Literal(Literal::Int(format!("0x{value:016X}")))
}
fn wide_signed(value: i128) -> Self::Expr {
Self::wide_int(value)
}
fn unsigned_int(value: i128) -> Self::Expr {
Expr::int(value)
}
fn literal(value: Literal) -> Self::Expr {
if let Literal::Int(text) = &value {
match text.as_str() {
"true" => return Expr::bool(true),
"false" => return Expr::bool(false),
_ => {}
}
}
Expr::Literal(value)
}
fn call(callee: &str, args: Vec<Self::Expr>) -> Self::Expr {
if callee == "strcmp" {
return Self::runtime_call("strcmp", args);
}
Self::codec_call(callee, args)
}
fn runtime(receiver: &str, method: &str, args: Vec<Self::Expr>) -> Self::Expr {
if method == "read_pid" && args.is_empty() {
return Self::runtime(
receiver,
"read_pid_as",
vec![Self::constant(Constant::ParameterMember)],
);
}
let mut all = vec![Self::name(receiver)];
all.extend(args);
Self::runtime_call(method, all)
}
fn runtime_static(method: &str, args: Vec<Self::Expr>) -> Self::Expr {
Self::runtime_call(method, args)
}
fn carries_hdds_axes() -> bool {
true
}
fn member(base: &str, field: &str) -> Self::Expr {
let mut place = Place::name(base);
for segment in field.split('.') {
place = place.field(segment);
}
Expr::place(place)
}
fn inherited_path(depth: usize) -> Option<String> {
Some("parent.".repeat(depth))
}
fn whole(base: &str) -> Self::Expr {
Expr::place(Place::name(base))
}
fn field_of(value: Self::Expr, field: &str) -> Self::Expr {
Expr::place(Self::place_of(value).field(field))
}
fn index(value: Self::Expr, at: Self::Expr) -> Self::Expr {
Expr::place(Place::Index {
base: Box::new(Self::place_of(value)),
key: Box::new(Expr::Binary {
op: "+",
lhs: Box::new(Expr::Paren(Box::new(at))),
rhs: Box::new(Expr::int(1)),
}),
})
}
fn count_of(base: &str, fixed: Option<u64>) -> Self::Expr {
match fixed {
Some(bound) => Expr::int(i128::from(bound)),
None => Expr::Unary {
op: "#",
value: Box::new(Expr::place(Place::name(base))),
},
}
}
fn elements(base: &str) -> Self::Expr {
Expr::place(Place::name(base))
}
fn binary(op: &'static str, lhs: Self::Expr, rhs: Self::Expr) -> Self::Expr {
Expr::Binary {
op: match op {
"&&" => "and",
"||" => "or",
"!=" => "~=",
other => other,
},
lhs: Box::new(lhs),
rhs: Box::new(rhs),
}
}
fn unary(op: &'static str, value: Self::Expr) -> Self::Expr {
Expr::Unary {
op: match op {
"!" => "not",
other => other,
},
value: Box::new(value),
}
}
fn paren(value: Self::Expr) -> Self::Expr {
Expr::Paren(Box::new(value))
}
fn cast(type_: Self::Type, value: Self::Expr) -> Self::Expr {
if type_.0 == SIGNED_CHAR {
return Expr::Paren(Box::new(Expr::Binary {
op: "-",
lhs: Box::new(Expr::Paren(Box::new(Expr::Binary {
op: "%",
lhs: Box::new(Expr::Paren(Box::new(Expr::Binary {
op: "+",
lhs: Box::new(value),
rhs: Box::new(Expr::int(128)),
}))),
rhs: Box::new(Expr::int(256)),
}))),
rhs: Box::new(Expr::int(128)),
}));
}
if type_.0 == "boolean" {
if let Expr::Literal(Literal::Int(text)) = &value {
if text.chars().all(|c| c.is_ascii_digit() || c == '-') && !text.is_empty() {
return Expr::bool(text != "0");
}
}
}
value
}
fn handoff(value: Self::Expr, _form: Handoff) -> Self::Expr {
value
}
fn declare(_type_: Self::Type, name: &str, value: Option<Self::Expr>) -> Self::Stmt {
Stat::Local {
doc: Doc::none(),
name: Ident::new(name),
value,
}
}
fn perform(value: Self::Expr) -> Self::Stmt {
if let Expr::Binary { op: "=", lhs, rhs } = value {
return Stat::Assign {
doc: Doc::none(),
target: Self::place_of(*lhs),
value: *rhs,
};
}
Stat::Perform(value)
}
fn give_back(value: Option<Self::Expr>) -> Self::Stmt {
Stat::Return(Some(value.unwrap_or_else(|| Expr::place(Place::name("v")))))
}
fn when(
condition: Self::Expr,
then: Self::Block,
otherwise: Option<Self::Block>,
) -> Self::Stmt {
Stat::If {
condition,
then,
otherwise: otherwise.filter(|body| !body.is_empty()),
}
}
fn count_up(counter: &str, bound: Self::Expr, body: Self::Block) -> Self::Stmt {
Stat::NumericFor {
counter: Ident::new(counter),
from: Expr::int(0),
to: Expr::Binary {
op: "-",
lhs: Box::new(bound),
rhs: Box::new(Expr::int(1)),
},
body,
}
}
fn until(condition: Self::Expr, body: Self::Block) -> Self::Stmt {
Stat::While { condition, body }
}
fn leave() -> Self::Stmt {
Stat::Break
}
fn note(doc: Doc) -> Self::Stmt {
Stat::Comment(doc)
}
fn drop_value(value: Self::Expr) -> Self::Stmt {
Stat::Perform(value)
}
fn mark_used(_name: &str) -> Vec<Self::Stmt> {
Vec::new()
}
fn read_call(
callee: &str,
place: Self::Expr,
_form: Handoff,
behaviour: Self::Expr,
) -> Self::Stmt {
Stat::Assign {
doc: Doc::none(),
target: Self::place_of(place),
value: Self::codec_call(callee, vec![Self::name("r"), behaviour]),
}
}
fn read_forward(callee: &str) -> Self::Stmt {
Self::read_call(
callee,
Expr::place(Place::name("v")),
Handoff::Value,
Self::name("zd_tc"),
)
}
fn block(statements: Vec<Self::Stmt>) -> Self::Block {
statements
}
fn into_statements(block: Self::Block) -> Vec<Self::Stmt> {
block
}
fn make_room(
base: &str,
_element: Self::Type,
count: Self::Expr,
assigned_whole: bool,
fill: Vec<Self::Stmt>,
) -> Vec<Self::Stmt> {
let mut out = vec![Stat::Assign {
doc: Doc::none(),
target: Self::place_from_name(base),
value: Expr::Table(lua::Table::default()),
}];
if !assigned_whole {
out.push(Stat::NumericFor {
counter: Ident::new("zd_e"),
from: Expr::int(0),
to: Expr::Binary {
op: "-",
lhs: Box::new(count),
rhs: Box::new(Expr::int(1)),
},
body: vec![Stat::Assign {
doc: Doc::none(),
target: Place::Index {
base: Box::new(Place::name(base)),
key: Box::new(Expr::Binary {
op: "+",
lhs: Box::new(Expr::place(Place::name("zd_e"))),
rhs: Box::new(Expr::int(1)),
}),
},
value: Expr::Table(lua::Table::default()),
}],
});
}
out.extend(fill);
out
}
fn keep_first(base: &str, count: Self::Expr) -> Vec<Self::Stmt> {
vec![Stat::While {
condition: Expr::Binary {
op: ">",
lhs: Box::new(Expr::Unary {
op: "#",
value: Box::new(Expr::place(Place::name(base))),
}),
rhs: Box::new(count),
},
body: vec![Stat::Assign {
doc: Doc::none(),
target: Place::Index {
base: Box::new(Place::name(base)),
key: Box::new(Expr::Unary {
op: "#",
value: Box::new(Expr::place(Place::name(base))),
}),
},
value: Expr::Nil,
}],
}]
}
fn sequence_element(
base: &str,
index: Self::Expr,
_element: Self::Type,
) -> (Vec<Self::Stmt>, Self::Expr, Vec<Self::Stmt>) {
(
Vec::new(),
Self::index(Self::elements(base), index),
Vec::new(),
)
}
fn present(base: &str, field: &str) -> Self::Expr {
Expr::Binary {
op: "~=",
lhs: Box::new(Self::member(base, field)),
rhs: Box::new(Expr::Nil),
}
}
fn set_present(base: &str, field: &str, present: bool) -> Vec<Self::Stmt> {
if present {
return Vec::new();
}
vec![Stat::Assign {
doc: Doc::none(),
target: Place::name(base).field(field),
value: Expr::Nil,
}]
}
fn clear_union_cases(base: &str, fields: &[String]) -> Vec<Self::Stmt> {
fields
.iter()
.flat_map(|field| Self::set_present(base, field, false))
.collect()
}
fn optional_destination(base: &str, field: &str) -> (Vec<Self::Stmt>, Self::Expr) {
(Vec::new(), Self::member(base, field))
}
fn optional_value(_ast: &Ast, _id: Id<TypeNode>, base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn case_member(base: &str, field: &str) -> Self::Expr {
Self::member(base, field)
}
fn entry_half(entry: Self::Expr, key: bool) -> Self::Expr {
Self::field_of(entry, if key { "key" } else { "value" })
}
fn absent() -> Self::Expr {
Expr::Nil
}
fn materialised(_value: Self::Expr) -> Option<Self::Expr> {
None
}
fn storage(type_: Self::Type, name: &str) -> Self::Stmt {
if type_.0 == MAP_ENTRY {
return Stat::Local {
doc: Doc::none(),
name: Ident::new(name),
value: Some(Expr::Table(lua::Table::default())),
};
}
Stat::Local {
doc: Doc::none(),
name: Ident::new(name),
value: None,
}
}
fn routine(spec: Routine<Self>) -> Option<Self::Decl> {
let body = spec.body?;
let mut params = vec![Ident::new(if spec.writing { "w" } else { "r" })];
if spec.writing {
params.push(Ident::new("v"));
}
for (_, name) in &spec.extra {
params.push(Ident::new(name));
}
let body = if spec.writing {
body
} else {
Self::reading_body(&spec.name, body)
};
Some(Stat::Function(lua::Function {
doc: spec.doc,
target: Place::name(CODECS).field(&spec.name),
params,
body,
}))
}
fn default_into(
ast: &Ast,
id: Id<TypeNode>,
place: Self::Expr,
) -> Result<Self::Stmt, EncodeError> {
Ok(crate::default_lua::default_into(ast, id, place))
}
fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: Self::Expr,
) -> Result<Vec<Self::Stmt>, EncodeError> {
crate::default_lua::member_default(ast, node, type_, place)
}
fn enumerator_constant(ast: &Ast, id: Id<TypeNode>, enumerator: &str) -> Self::Expr {
match declared_place(ast, id) {
Ok(place) => Expr::place(place.field("values").field(enumerator)),
Err(_) => Self::name(enumerator),
}
}
}
fn value_type_of(ast: &Ast, id: Id<TypeNode>, depth: usize) -> LuaType {
use zerodds_ast::PrimitiveKind;
if depth > MAX_TYPE_DEPTH {
return LuaType("table".to_string());
}
let Ok(node) = ast.type_(id) else {
return LuaType("table".to_string());
};
LuaType(
match &node.kind {
TypeKind::Primitive(kind) => match kind {
PrimitiveKind::Boolean => "boolean",
PrimitiveKind::Float32 | PrimitiveKind::Float64 => "number",
PrimitiveKind::Float128 => "string",
_ => "integer",
},
TypeKind::String { .. } | TypeKind::Fixed { .. } => "string",
TypeKind::Enumeration { .. } | TypeKind::Bitmask { .. } => "integer",
TypeKind::Alias { aliased } => {
return aliased.resolved().map_or_else(
|| LuaType("table".to_string()),
|target| value_type_of(ast, target, depth + 1),
);
}
TypeKind::ValueBox { boxed } => {
return boxed.resolved().map_or_else(
|| LuaType("table".to_string()),
|target| value_type_of(ast, target, depth + 1),
);
}
_ => "table",
}
.to_string(),
)
}
fn bound_tag(ast: &Ast, bound: &zerodds_ast::Bound) -> String {
match bound.expr() {
None => String::new(),
Some(expr) => match ast.eval_integral(expr) {
Ok(limit) => format!("_b{limit}"),
Err(_) => "_bx".to_string(),
},
}
}
fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> String {
if depth > MAX_TYPE_DEPTH {
return "unknown".to_string();
}
let Ok(node) = ast.type_(id) else {
return "unknown".to_string();
};
match &node.kind {
TypeKind::Primitive(kind) => kind.as_str().replace(' ', "_"),
TypeKind::String {
width: CharWidth::Char,
bound,
} => format!("string{}", bound_tag(ast, bound)),
TypeKind::String {
width: CharWidth::WChar,
bound,
} => format!("wstring{}", bound_tag(ast, bound)),
TypeKind::Fixed { digits, scale } => format!("fixed_{digits}_{scale}"),
TypeKind::Sequence { element, bound } => match element.resolved() {
Some(element) => format!(
"seq{}_{}",
bound_tag(ast, bound),
type_tag(ast, element, depth + 1)
),
None => format!("seq_unknown{}", bound_tag(ast, bound)),
},
TypeKind::Array { element, dims } => {
let mut name = match element.resolved() {
Some(element) => format!("arr_{}", type_tag(ast, element, depth + 1)),
None => "arr_unknown".to_string(),
};
for dim in dims {
match ast.eval_integral(*dim) {
Ok(length) => name.push_str(&format!("_{length}")),
Err(_) => name.push_str("_x"),
}
}
name
}
TypeKind::Map { key, value, bound } => {
let half = |half: Option<Id<TypeNode>>| match half {
Some(target) => type_tag(ast, target, depth + 1),
None => "unknown".to_string(),
};
format!(
"map{}_{}_{}",
bound_tag(ast, bound),
half(key.resolved()),
half(value.resolved())
)
}
TypeKind::Alias { aliased } => {
if !node.name.is_empty() {
let scoped = crate::adapter::lua::scoped_name(ast, id);
return scoped.trim_start_matches(':').replace("::", "_");
}
match aliased.resolved() {
Some(target) => type_tag(ast, target, depth + 1),
None => "unknown".to_string(),
}
}
TypeKind::ValueBox { boxed } => match boxed.resolved() {
Some(target) => format!("box_{}", type_tag(ast, target, depth + 1)),
None => "box_unknown".to_string(),
},
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native => {
let scoped = crate::adapter::lua::scoped_name(ast, id);
scoped
.trim_start_matches(':')
.replace("::", "_")
.replace(['<', '>'], "_")
}
TypeKind::Any => "any".to_string(),
TypeKind::ValueBase => "valuebase".to_string(),
TypeKind::ObjectRef { .. } => "objectref".to_string(),
TypeKind::AnnotationType { .. } => "annotation".to_string(),
}
}