use crate::{
context::{Context, Shared},
space::Space,
value::{
BasicBlock, FunctionBody, LocalBlockId, LocalValueId, QCodeView, ValueId,
block::BlockId,
bytes::BytesRef,
function::FunctionId,
insn::{Callee, InstructionRef, Mnemonic},
literal::{LiteralId, LiteralRef, SymbolicRef},
varnode::Varnode,
},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
Type,
Variable,
BlockParam,
Varnode,
Literal,
Bytes,
Keyword,
Operator,
Punctuation,
Label,
Field,
Function,
Space,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Link {
Value(ValueId),
Function(FunctionId),
Block(BlockId),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Token {
pub text: String,
pub kind: TokenKind,
pub link: Option<Link>,
}
impl Token {
fn new(text: impl Into<String>, kind: TokenKind, link: Option<Link>) -> Self {
Token {
text: text.into(),
kind,
link,
}
}
}
struct Seg<'ctx, 'str, R> {
view: R,
out: Vec<Token>,
marker: std::marker::PhantomData<&'ctx &'str ()>,
}
impl<'ctx, 'str: 'ctx, R> Seg<'ctx, 'str, R>
where
R: QCodeView<'ctx, 'str>,
{
fn push(&mut self, text: impl Into<String>, kind: TokenKind, link: Option<Link>) {
self.out.push(Token::new(text, kind, link));
}
fn kw(&mut self, text: &str) {
self.push(text, TokenKind::Keyword, None);
}
fn op(&mut self, text: impl Into<String>) {
self.push(text, TokenKind::Operator, None);
}
fn punct(&mut self, text: &str) {
self.push(text, TokenKind::Punctuation, None);
}
fn ty(&mut self, type_id: crate::types::TypeId) {
self.push(
format!("{} ", self.view.shared().types.type_name(type_id)),
TokenKind::Type,
None,
);
}
fn value(&mut self, id: ValueId) {
let link = Some(Link::Value(id));
match id {
ValueId::Instruction(iid) => {
let r = self.view.insn_ref(iid);
self.ty(r.type_id());
self.push(instruction_atom(self.view, iid), TokenKind::Variable, link);
}
ValueId::BlockParam(pid) => {
let r = self.view.param_ref(pid);
self.ty(r.type_id());
self.push(
block_param_atom(self.view, pid),
TokenKind::BlockParam,
link,
);
}
ValueId::Literal(lid) => {
let r = LiteralRef::from_id(self.view.shared(), lid);
self.ty(r.type_id());
self.push(literal_atom_view(self.view, lid), TokenKind::Literal, link);
}
ValueId::Bytes(bid) => {
let r = BytesRef::from_id(self.view.shared(), bid);
self.ty(r.type_id());
self.push(r.to_string(), TokenKind::Bytes, link);
}
ValueId::Varnode(vid) => {
let r = Varnode::from_id(self.view.shared(), vid);
self.push(format!("i{} ", r.size() * 8), TokenKind::Type, None);
self.push(r.to_string(), TokenKind::Varnode, link);
}
ValueId::Poison(pid) => {
let ty = self.view.shared().values.poisons[pid].type_id;
self.ty(ty);
self.push("poison".to_string(), TokenKind::Literal, link);
}
ValueId::Temp(id) => {
let r = self.view.temp_ref(id);
self.push(format!("i{} ", r.size() * 8), TokenKind::Type, None);
self.push(r.to_string(), TokenKind::Varnode, link);
}
ValueId::Function(fid) => {
let name = self.view.interface(fid).name.to_string();
self.push(
format!("<{name}>"),
TokenKind::Function,
Some(Link::Function(fid)),
);
}
ValueId::BasicBlock(bid) => {
let text = if self.view.owner().is_some_and(|o| o != bid.func) {
format!("<{bid}>")
} else {
self.view.block_ref(bid).to_string()
};
self.push(text, TokenKind::Label, Some(Link::Block(bid)));
}
}
}
fn bare_value(&mut self, id: ValueId) {
match id {
ValueId::Instruction(iid) => {
self.push(
instruction_atom(self.view, iid),
TokenKind::Variable,
Some(Link::Value(id)),
);
}
other => self.value(other),
}
}
fn branch_target(&mut self, func: FunctionId, target: LocalBlockId, args: &[LocalValueId]) {
let target = BlockId::new(func, target);
let block = self.view.block_ref(target);
let name = block.name().unwrap_or("unnamed");
self.push(
format!("<{name}"),
TokenKind::Label,
Some(Link::Block(target)),
);
let params = block.params().collect::<Vec<_>>();
for (i, &arg) in args.iter().enumerate() {
self.punct(" ");
match params.get(i) {
Some(param) => self.push(param.to_string(), TokenKind::BlockParam, None),
None => self.push(format!("@arg{i}"), TokenKind::BlockParam, None),
}
self.op("=");
self.value(arg.qualify(func));
}
self.push(">", TokenKind::Label, None);
}
}
fn instruction_atom<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
id: crate::value::InstructionId,
) -> String {
match view.instruction(id).name.as_deref() {
Some(name) => format!("%{name}"),
None => format!("%tmp{:x}", usize::from(id.local)),
}
}
fn block_param_atom<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
id: crate::value::BlockParamId,
) -> String {
let r = view.param_ref(id);
match r.name() {
Some(name) => format!("@{name}"),
None => format!("@param{:x}", usize::from(id.local)),
}
}
pub fn instruction_segments<'ctx, 'str: 'ctx, R>(insn: &InstructionRef<'str, 'ctx, R>) -> Vec<Token>
where
R: QCodeView<'ctx, 'str>,
{
let view = insn.view;
let mut seg = Seg {
view,
out: Vec::new(),
marker: std::marker::PhantomData,
};
if insn.size() != 0 {
seg.ty(insn.type_id());
seg.push(
instruction_atom(view, insn.id),
TokenKind::Variable,
Some(Link::Value(ValueId::Instruction(insn.id))),
);
seg.op(" = ");
}
match insn.mnemonic() {
Mnemonic::Tuple(t) => tuple_with_type(&mut seg, insn.id.func, t, insn.type_id()),
m => mnemonic_segments(&mut seg, insn.id.func, m),
}
seg.out
}
fn tuple_with_type<'ctx, 'str: 'ctx>(
seg: &mut Seg<'ctx, 'str, impl QCodeView<'ctx, 'str>>,
func: FunctionId,
t: &crate::value::insn::Tuple,
type_id: crate::types::TypeId,
) {
seg.kw("pack");
seg.punct("(");
for (i, &field) in t.fields.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
let name = seg
.view
.shared()
.types
.field_name(type_id, i)
.map(str::to_owned)
.unwrap_or_else(|| format!("field{}", i + 1));
seg.push(name, TokenKind::Field, None);
seg.op("=");
seg.value(field.qualify(func));
}
seg.punct(");");
}
fn mnemonic_segments<'ctx, 'str: 'ctx>(
seg: &mut Seg<'ctx, 'str, impl QCodeView<'ctx, 'str>>,
func: FunctionId,
m: &Mnemonic,
) {
use crate::value::insn::Unop;
match m {
Mnemonic::Load(l) => {
seg.kw("load");
seg.punct("(");
seg.push(space_name(seg.view, func, l.space), TokenKind::Space, None);
seg.punct(":");
seg.push(l.size.to_string(), TokenKind::Type, None);
seg.punct(", ");
seg.value(l.ptr.qualify(func));
seg.punct(");");
}
Mnemonic::Store(s) => {
seg.kw("store");
seg.punct("(");
seg.push(space_name(seg.view, func, s.space), TokenKind::Space, None);
seg.punct(":");
seg.push(s.size.to_string(), TokenKind::Type, None);
seg.punct(", ");
seg.value(s.ptr.qualify(func));
seg.op(" <- ");
seg.value(s.src.qualify(func));
seg.punct(");");
}
Mnemonic::Branch(b) => {
seg.kw("goto ");
seg.branch_target(func, b.target, &b.args);
seg.punct(";");
}
Mnemonic::BranchInd(b) => {
seg.kw("goto ");
seg.punct("[");
seg.value(b.ptr.qualify(func));
seg.punct("];");
}
Mnemonic::Switch(sw) => {
seg.kw("switch ");
seg.value(sw.scrutinee.qualify(func));
seg.punct(" { ");
for (i, case) in sw.cases.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.push(format!("{:#x}", case.value), TokenKind::Literal, None);
seg.op(" => ");
seg.branch_target(func, case.target, &case.args);
}
if let Some(default) = sw.default {
if !sw.cases.is_empty() {
seg.punct(", ");
}
seg.kw("default");
seg.op(" => ");
seg.branch_target(func, default, &sw.default_args);
}
seg.punct(" };");
}
Mnemonic::CBranch(cb) => {
seg.kw("if ");
seg.value(cb.condition.qualify(func));
seg.kw(" goto ");
seg.branch_target(func, cb.success_block, &cb.success_args);
seg.kw(" else goto ");
seg.branch_target(func, cb.failure_block, &cb.failure_args);
seg.punct(";");
}
Mnemonic::Apply(a) => {
seg.kw("apply ");
let (target, link) = callee_name_link(seg.view, a.target);
seg.push(target, TokenKind::Function, link);
seg.punct("(");
for (i, &arg) in a.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.value(arg.qualify(func));
}
seg.punct(");");
}
Mnemonic::Call(c) => {
seg.kw("call fn ");
let (target, link) = callee_name_link(seg.view, c.target);
seg.push(target, TokenKind::Function, link);
seg.punct("(");
for (i, &arg) in c.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
let arg_name = c
.target
.real()
.map(|target| call_arg_name(seg.view, target, i))
.unwrap_or_else(|| format!("@arg{i}="));
seg.push(arg_name, TokenKind::BlockParam, None);
seg.value(arg.qualify(func));
}
seg.punct(");");
}
Mnemonic::TailCall(tc) => {
seg.kw("tailcall fn ");
let (target, link) = callee_name_link(seg.view, tc.target);
seg.push(target, TokenKind::Function, link);
seg.punct("(");
for (i, &arg) in tc.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.value(arg.qualify(func));
}
seg.punct(");");
}
Mnemonic::CallInd(c) => {
seg.kw("call ");
seg.punct("[");
seg.value(c.ptr.qualify(func));
seg.punct("]");
if !c.args.is_empty() {
seg.punct("(");
for (i, &arg) in c.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.value(arg.qualify(func));
}
seg.punct(")");
}
seg.punct(";");
}
Mnemonic::BadInsn(_) => {
seg.kw("badinsn");
seg.punct(";");
}
Mnemonic::Return(r) => match r.value {
Some(value) => {
seg.kw("return ");
seg.value(value.qualify(func));
seg.kw(" at ");
seg.value(r.ptr.qualify(func));
seg.punct(";");
}
None => {
seg.kw("return at ");
seg.value(r.ptr.qualify(func));
seg.punct(";");
}
},
Mnemonic::ReturnValue(r) => {
seg.kw("return ");
seg.value(r.value.qualify(func));
seg.punct(";");
}
Mnemonic::Unop(u) => match u.op {
Unop::IntNegate | Unop::IntNot | Unop::FloatNegate => {
seg.op(format!("{} ", u.op));
seg.value(u.src.qualify(func));
seg.punct(";");
}
_ => {
seg.kw(&u.op.to_string());
seg.punct("(");
seg.value(u.src.qualify(func));
seg.punct(");");
}
},
Mnemonic::Binop(b) => {
seg.value(b.lhs.qualify(func));
seg.op(format!(" {} ", b.op));
seg.value(b.rhs.qualify(func));
seg.punct(";");
}
Mnemonic::Zext(z) => cast(seg, func, "zext", 'i', z.size, z.src),
Mnemonic::Sext(s) => cast(seg, func, "sext", 'i', s.size, s.src),
Mnemonic::IntToFloat(c) => cast(seg, func, "int2float", 'f', c.size, c.src),
Mnemonic::FloatToFloat(c) => cast(seg, func, "float2float", 'f', c.size, c.src),
Mnemonic::FloatToInt(c) => cast(seg, func, "trunc", 'i', c.size, c.src),
Mnemonic::Range(r) => {
seg.value(r.src.qualify(func));
seg.punct("[");
seg.push(r.start.to_string(), TokenKind::Literal, None);
seg.punct(":");
seg.push((r.start + r.size).to_string(), TokenKind::Literal, None);
seg.punct("];");
}
Mnemonic::IsFloatNaN(o) => unary_call(seg, func, "nan", o.src),
Mnemonic::LzCount(o) => unary_call(seg, func, "lzcount", o.src),
Mnemonic::PopCount(o) => unary_call(seg, func, "popcount", o.src),
Mnemonic::Carry(o) => binary_call(seg, func, "carry", o.lhs, o.rhs),
Mnemonic::SCarry(o) => binary_call(seg, func, "scarry", o.lhs, o.rhs),
Mnemonic::SBorrow(o) => binary_call(seg, func, "sborrow", o.lhs, o.rhs),
Mnemonic::Assert(a) => {
seg.kw("assert ");
seg.value(a.condition.qualify(func));
seg.punct(";");
}
Mnemonic::Tuple(t) => {
seg.kw("pack");
seg.punct("(");
for (i, &field) in t.fields.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.push(format!("field{}", i + 1), TokenKind::Field, None);
seg.op("=");
seg.value(field.qualify(func));
}
seg.punct(");");
}
Mnemonic::Extract(e) => {
seg.kw("extract");
seg.punct("(");
seg.bare_value(e.agg.qualify(func));
let name = e
.field_name_view(seg.view, func)
.map(str::to_owned)
.unwrap_or_else(|| format!("field{}", e.index + 1));
seg.push(format!(".{name}"), TokenKind::Field, None);
seg.punct(");");
}
Mnemonic::Gep(g) => {
seg.kw("gep");
seg.punct("(");
seg.bare_value(g.base.qualify(func));
match g.field_name_view(seg.view, func) {
Some(name) => seg.push(format!(".{name}"), TokenKind::Field, None),
None => {
seg.op(" + ");
seg.push(format!("{:#x}", g.offset), TokenKind::Literal, None);
}
}
seg.punct(");");
}
Mnemonic::Map(map) => {
let (body, link) = callee_name_link(seg.view, map.body);
if map.captures.is_empty() {
seg.push(body, TokenKind::Function, link);
seg.op(" <$> ");
seg.value(map.src.qualify(func));
seg.punct(";");
} else {
seg.punct("(");
seg.push(body, TokenKind::Function, link);
for &c in &map.captures {
seg.punct(" ");
seg.value(c.qualify(func));
}
seg.op(") <$> ");
seg.value(map.src.qualify(func));
seg.punct(";");
}
}
Mnemonic::Scan(scan) => {
let (body, link) = callee_name_link(seg.view, scan.body);
let body = match scan.body {
Callee::Real(_) => format!("@{body}"),
Callee::Minted(_) => body,
};
if scan.captures.is_empty() {
seg.kw("scanl ");
seg.push(body, TokenKind::Function, link);
seg.punct(" ");
seg.value(scan.init.qualify(func));
seg.punct(" ");
seg.value(scan.src.qualify(func));
seg.punct(";");
} else {
seg.kw("scanl ");
seg.punct("(");
seg.push(body, TokenKind::Function, link);
for &c in &scan.captures {
seg.punct(" ");
seg.value(c.qualify(func));
}
seg.punct(") ");
seg.value(scan.init.qualify(func));
seg.punct(" ");
seg.value(scan.src.qualify(func));
seg.punct(";");
}
}
Mnemonic::PCodeOp(p) => {
let op = seg.view.shared().pcode_ops[p.id].to_string();
if let Some(dst) = p.dst {
seg.value(dst.qualify(func));
seg.op(" = ");
}
seg.kw(&op);
seg.punct("(");
for (i, &arg) in p.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.value(arg.qualify(func));
}
seg.punct(");");
}
Mnemonic::Intrinsic(intr) => {
seg.kw(&format!("${}", intr.id.name()));
seg.punct("(");
for (i, &arg) in intr.args.iter().enumerate() {
if i > 0 {
seg.punct(", ");
}
seg.value(arg.qualify(func));
}
seg.punct(");");
}
}
}
fn cast<'ctx, 'str: 'ctx>(
seg: &mut Seg<'ctx, 'str, impl QCodeView<'ctx, 'str>>,
func: FunctionId,
kw: &str,
prefix: char,
size: usize,
src: LocalValueId,
) {
seg.kw(kw);
seg.punct("(");
seg.push(format!("{prefix}{}", size * 8), TokenKind::Type, None);
seg.punct(", ");
seg.value(src.qualify(func));
seg.punct(");");
}
fn unary_call<'ctx, 'str: 'ctx>(
seg: &mut Seg<'ctx, 'str, impl QCodeView<'ctx, 'str>>,
func: FunctionId,
kw: &str,
src: LocalValueId,
) {
seg.kw(kw);
seg.punct("(");
seg.value(src.qualify(func));
seg.punct(");");
}
fn binary_call<'ctx, 'str: 'ctx>(
seg: &mut Seg<'ctx, 'str, impl QCodeView<'ctx, 'str>>,
func: FunctionId,
kw: &str,
lhs: LocalValueId,
rhs: LocalValueId,
) {
seg.kw(kw);
seg.punct("(");
seg.value(lhs.qualify(func));
seg.punct(", ");
seg.value(rhs.qualify(func));
seg.punct(");");
}
fn space_name<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
func: FunctionId,
space: crate::space::LocalMemorySpaceId,
) -> String {
match space.qualify(func) {
crate::space::MemorySpaceId::Shared(space) => {
let space_ref = Space::from_id(view.shared(), space);
match space_ref.name.as_deref() {
Some(name) => name.to_string(),
None => format!("space: {space}"),
}
}
crate::space::MemorySpaceId::Temp(space) => {
format!("$temp{}", usize::from(space.local))
}
}
}
fn call_arg_name<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
target: FunctionId,
index: usize,
) -> String {
let foreign = view.owner().is_some_and(|owner| owner != target);
let name = if foreign {
view.interface(target)
.signature
.as_ref()
.and_then(|s| s.extern_interface.as_ref())
.and_then(|iface| iface.args.get(index))
.and_then(|a| a.name.as_ref().map(|n| n.to_string()))
} else {
view.function_ref(target).input_arg_name(index)
};
match name {
Some(name) => format!("@{name}="),
None => format!("@arg{index}="),
}
}
fn callee_name_link<'ctx, 'str: 'ctx>(
view: impl QCodeView<'ctx, 'str>,
callee: Callee,
) -> (String, Option<Link>) {
match callee {
Callee::Real(id) => (
view.interface(id).name.to_string(),
Some(Link::Function(id)),
),
Callee::Minted(slot) => (format!("<minted:{slot}>"), None),
}
}
pub fn value_tokens(ctx: &Context<'_>, id: ValueId) -> Vec<Token> {
let mut seg = Seg {
view: crate::value::ModuleView::new(ctx),
out: Vec::new(),
marker: std::marker::PhantomData,
};
seg.value(id);
seg.out
}
pub fn value_tokens_view<'ctx, 'str: 'ctx, R>(view: R, id: ValueId) -> Vec<Token>
where
R: QCodeView<'ctx, 'str>,
{
let link = Some(Link::Value(id));
let shared = view.shared();
let mut out = Vec::new();
let mut typed = |type_id, text: String, kind| {
out.push(Token::new(
format!("{} ", shared.types.type_name(type_id)),
TokenKind::Type,
None,
));
out.push(Token::new(text, kind, link));
};
match id {
ValueId::Instruction(iid) => {
let insn = view.instruction(iid);
let atom = insn.name.as_deref().map_or_else(
|| {
let local: usize = iid.local.into();
format!("%tmp{local:x}")
},
|name| format!("%{name}"),
);
typed(insn.type_id, atom, TokenKind::Variable);
}
ValueId::BlockParam(pid) => {
let param = view.block_param(pid);
let atom = param.name.as_deref().map_or_else(
|| {
let local: usize = pid.local.into();
format!("@param{local:x}")
},
|name| format!("@{name}"),
);
typed(param.type_id, atom, TokenKind::BlockParam);
}
ValueId::Literal(lid) => {
let literal = &shared.values.literals[lid];
let atom = literal_atom_view(view, lid);
typed(literal.type_id, atom, TokenKind::Literal);
}
ValueId::Bytes(id) => {
let value = BytesRef::from_id(shared, id);
typed(value.type_id(), value.to_string(), TokenKind::Bytes);
}
ValueId::Varnode(id) => {
let value = Varnode::from_id(shared, id);
out.push(Token::new(
format!("i{} ", value.size() * 8),
TokenKind::Type,
None,
));
out.push(Token::new(value.to_string(), TokenKind::Varnode, link));
}
ValueId::Temp(id) => {
let value = view.temp_ref(id);
out.push(Token::new(
format!("i{} ", value.size() * 8),
TokenKind::Type,
None,
));
out.push(Token::new(value.to_string(), TokenKind::Varnode, link));
}
ValueId::Function(id) => out.push(Token::new(
format!("<{}>", view.interface(id).name),
TokenKind::Function,
Some(Link::Function(id)),
)),
ValueId::BasicBlock(id) => out.push(Token::new(
view.block_ref(id).to_string(),
TokenKind::Label,
Some(Link::Block(id)),
)),
ValueId::Poison(id) => {
typed(
shared.values.poisons[id].type_id,
"poison".to_string(),
TokenKind::Literal,
);
}
}
out
}
pub fn literal_atom(ctx: &Context<'_>, id: LiteralId) -> String {
let literal = &ctx.shared.values.literals[id];
match &literal.symbolic {
Some(SymbolicRef::Block(bid)) => match BasicBlock::from_id(ctx, *bid).name() {
Some(name) => format!("&<{}>", name),
None => format!("&<0x{:x}>", literal.value),
},
Some(SymbolicRef::Function(fid)) => {
format!("&<{}>", FunctionBody::from_id(ctx, *fid).name())
}
Some(SymbolicRef::String(s)) => format!("&{:?}", s),
None if ctx.shared.types.is_bool(literal.type_id) => {
(if literal.value != 0 { "true" } else { "false" }).to_string()
}
None => format!("0x{:x}", literal.value),
}
}
fn literal_atom_view<'ctx, 'str: 'ctx>(view: impl QCodeView<'ctx, 'str>, id: LiteralId) -> String {
let shared = view.shared();
let literal = &shared.values.literals[id];
match &literal.symbolic {
Some(SymbolicRef::Block(id)) if view.owner().is_some_and(|o| o != id.func) => {
format!("&<0x{:x}>", literal.value)
}
Some(SymbolicRef::Block(id)) => view.block_ref(*id).name().map_or_else(
|| format!("&<0x{:x}>", literal.value),
|name| format!("&<{name}>"),
),
Some(SymbolicRef::Function(id)) => format!("&<{}>", view.interface(*id).name),
Some(SymbolicRef::String(value)) => format!("&{value:?}"),
None if shared.types.is_bool(literal.type_id) => {
(if literal.value != 0 { "true" } else { "false" }).to_string()
}
None => format!("0x{:x}", literal.value),
}
}
pub fn value_tokens_shared(shared: &Shared<'_>, id: ValueId) -> Vec<Token> {
let link = Some(Link::Value(id));
let mut out = Vec::new();
match id {
ValueId::Literal(lid) => {
let r = LiteralRef::from_id(shared, lid);
out.push(Token::new(
format!("{} ", shared.types.type_name(r.type_id())),
TokenKind::Type,
None,
));
out.push(Token::new(r.to_string(), TokenKind::Literal, link));
}
ValueId::Bytes(bid) => {
let r = BytesRef::from_id(shared, bid);
out.push(Token::new(
format!("{} ", shared.types.type_name(r.type_id())),
TokenKind::Type,
None,
));
out.push(Token::new(r.to_string(), TokenKind::Bytes, link));
}
ValueId::Varnode(vid) => {
let r = Varnode::from_id(shared, vid);
out.push(Token::new(
format!("i{} ", r.size() * 8),
TokenKind::Type,
None,
));
out.push(Token::new(r.to_string(), TokenKind::Varnode, link));
}
ValueId::Poison(pid) => {
let ty = shared.values.poisons[pid].type_id;
out.push(Token::new(
format!("{} ", shared.types.type_name(ty)),
TokenKind::Type,
None,
));
out.push(Token::new("poison".to_string(), TokenKind::Literal, link));
}
_ => panic!("value_tokens_shared: not a shared-leaf value id"),
}
out
}