use std::fmt::Debug;
use std::fmt::Display;
use debugserver_types::*;
use dupe::Dupe;
use crate::codemap::FileSpan;
use crate::eval::Evaluator;
use crate::syntax::AstModule;
use crate::values::dict::DictRef;
use crate::values::layout::heap::heap_type::Heap;
use crate::values::layout::value::Value;
mod implementation;
mod tests;
pub trait DapAdapterClient: Debug + Send + Sync + 'static {
fn event_stopped(&self) -> crate::Result<()>;
}
pub struct ScopesInfo {
pub num_locals: usize,
}
pub struct Variable {
pub name: PathSegment,
pub value: String,
pub type_: String,
pub has_children: bool,
}
#[derive(Clone, Debug)]
pub enum Scope {
Local(String),
#[allow(dead_code)]
Expr(String),
}
#[derive(Clone, Debug)]
pub struct VariablePath {
scope: Scope,
access_path: Vec<PathSegment>,
}
impl VariablePath {
pub fn new_expression(expr: impl Into<String>) -> VariablePath {
VariablePath {
scope: Scope::Expr(expr.into()),
access_path: vec![],
}
}
pub fn new_local(scope: impl Into<String>) -> VariablePath {
VariablePath {
scope: Scope::Local(scope.into()),
access_path: vec![],
}
}
pub fn make_child(&self, path: PathSegment) -> VariablePath {
let mut access_path = self.access_path.clone();
access_path.push(path);
Self {
scope: self.scope.clone(),
access_path,
}
}
}
#[derive(Clone, Debug)]
pub enum PathSegment {
Index(i32),
Attr(String),
Key(String),
}
impl Display for PathSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PathSegment::Index(x) => write!(f, "{}", x),
PathSegment::Attr(x) => f.write_str(x),
PathSegment::Key(x) => write!(f, "\"{}\"", x),
}
}
}
impl PathSegment {
fn get<'v>(&self, v: &Value<'v>, heap: &'v Heap) -> crate::Result<Value<'v>> {
match self {
PathSegment::Index(i) => v.at(heap.alloc(*i), heap).map_err(Into::into),
PathSegment::Attr(key) => v.get_attr_error(key.as_str(), heap),
PathSegment::Key(i) => v.at(heap.alloc(i.to_owned()), heap).map_err(Into::into),
}
}
}
impl Variable {
pub fn to_dap(self) -> debugserver_types::Variable {
debugserver_types::Variable {
name: self.name.to_string(),
value: self.value,
type_: Some(self.type_),
evaluate_name: None,
indexed_variables: None,
named_variables: None,
presentation_hint: None,
variables_reference: 0,
}
}
fn tuple_value_as_str<'v>(v: Value<'v>) -> String {
match v.length() {
Ok(size) if size > 0 => format!("<tuple, size={}>", size),
_ => "()".to_owned(),
}
}
fn list_value_as_str<'v>(v: Value<'v>) -> String {
match v.length() {
Ok(size) if size > 0 => format!("<list, size={}>", size),
_ => "[]".to_owned(),
}
}
fn dict_value_as_str<'v>(v: Value<'v>) -> String {
match v.length() {
Ok(size) if size > 0 => format!("<dict, size={}>", size),
_ => "{}".to_owned(),
}
}
fn struct_like_value_as_str<'v>(v: Value<'v>) -> String {
let attrs = v.dir_attr();
format!("<type:{}, size={}>", v.get_type(), attrs.len())
}
pub(crate) fn truncate_string(mut str_value: String, mut max_len: usize) -> String {
if str_value.len() > max_len {
while max_len > 0 && !str_value.is_char_boundary(max_len) {
max_len -= 1;
}
if max_len > 0 {
str_value.truncate(max_len);
str_value.push_str("...(truncated)");
}
}
str_value
}
pub(crate) fn value_as_str<'v>(v: &Value<'v>) -> String {
if Self::has_children(v) {
match v.get_type() {
"list" => Self::list_value_as_str(*v),
"tuple" => Self::tuple_value_as_str(*v),
"dict" => Self::dict_value_as_str(*v),
_ => Self::struct_like_value_as_str(*v),
}
} else {
match v.get_type() {
"function" => "<function>".to_owned(),
_ => {
const MAX_STR_LEN: usize = 10000;
Self::truncate_string(v.to_str(), MAX_STR_LEN)
}
}
}
}
pub fn from_value<'v>(name: PathSegment, v: Value<'v>) -> Self {
Self {
name,
value: Self::value_as_str(&v),
type_: v.get_type().to_owned(),
has_children: Self::has_children(&v),
}
}
pub(crate) fn has_children<'v>(v: &Value<'v>) -> bool {
match v.get_type() {
"function" | "never" | "NoneType" | "bool" | "int" | "float" | "string" => false,
"list" | "tuple" | "dict" => match v.length() {
Ok(length) => length > 0,
_ => false,
},
_ => true,
}
}
}
#[derive(Debug, Clone, Dupe, Copy)]
pub enum StepKind {
Into,
Over,
Out,
}
pub struct VariablesInfo {
pub locals: Vec<Variable>,
}
#[derive(Default)]
pub struct InspectVariableInfo {
pub sub_values: Vec<Variable>,
}
pub struct EvaluateExprInfo {
pub result: String,
pub type_: String,
pub has_children: bool,
}
impl InspectVariableInfo {
fn try_from_dict<'v>(value_dict: DictRef<'v>) -> crate::Result<Self> {
let key_segments = value_dict
.iter()
.map(|(key, value)| (PathSegment::Key(key.to_str()), value))
.collect::<Vec<_>>();
Ok(Self {
sub_values: key_segments
.into_iter()
.map(|(path_segment, value)| Variable::from_value(path_segment, value))
.collect::<Vec<_>>(),
})
}
fn try_from_struct_like<'v>(v: Value<'v>, heap: &'v Heap) -> crate::Result<Self> {
Ok(Self {
sub_values: v
.dir_attr()
.into_iter()
.map(|child_name| {
let child_value = v.get_attr_error(&child_name, heap)?;
let segment = PathSegment::Attr(child_name);
Ok(Variable::from_value(segment, child_value))
})
.collect::<crate::Result<Vec<_>>>()?,
})
}
fn try_from_array_like<'v>(v: Value<'v>, heap: &'v Heap) -> crate::Result<Self> {
let len = v.length()?;
Ok(Self {
sub_values: (0..len)
.map(|i| {
let index = heap.alloc(i);
v.at(index, heap)
.map(|v| Variable::from_value(PathSegment::Index(i), v))
})
.collect::<crate::Result<Vec<_>>>()?,
})
}
pub fn try_from_value<'v>(v: Value<'v>, heap: &'v Heap) -> crate::Result<Self> {
match v.get_type() {
"dict" => Self::try_from_dict(
DictRef::from_value(v).ok_or(anyhow::Error::msg("not a dictionary"))?,
),
"struct" => Self::try_from_struct_like(v, heap),
"list" | "tuple" => Self::try_from_array_like(v, heap),
"bool" | "int" | "float" | "string" => Ok(Default::default()),
"function" | "never" | "NoneType" => Ok(Default::default()),
_ => Self::try_from_struct_like(v, heap),
}
}
}
impl EvaluateExprInfo {
pub fn from_value(v: &Value) -> Self {
Self {
result: Variable::value_as_str(v),
type_: v.get_type().to_owned(),
has_children: Variable::has_children(v),
}
}
}
pub trait DapAdapter: Debug + Send + 'static {
fn set_breakpoints(
&self,
source: &str,
breakpoints: &ResolvedBreakpoints,
) -> anyhow::Result<()>;
fn top_frame(&self) -> anyhow::Result<Option<StackFrame>>;
fn stack_trace(&self, args: StackTraceArguments) -> anyhow::Result<StackTraceResponseBody>;
fn scopes(&self) -> anyhow::Result<ScopesInfo>;
fn variables(&self) -> anyhow::Result<VariablesInfo>;
fn inspect_variable(&self, path: VariablePath) -> anyhow::Result<InspectVariableInfo>;
fn continue_(&self) -> anyhow::Result<()>;
fn step(&self, kind: StepKind) -> anyhow::Result<()>;
fn evaluate(&self, expr: &str) -> anyhow::Result<EvaluateExprInfo>;
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub(crate) struct Breakpoint {
span: FileSpan,
condition: Option<String>,
}
#[derive(Debug)]
pub struct ResolvedBreakpoints(Vec<Option<Breakpoint>>);
impl ResolvedBreakpoints {
pub fn to_response(&self) -> SetBreakpointsResponseBody {
implementation::resolved_breakpoints_to_dap(self)
}
}
pub fn resolve_breakpoints(
args: &SetBreakpointsArguments,
ast: &AstModule,
) -> anyhow::Result<ResolvedBreakpoints> {
implementation::resolve_breakpoints(args, ast)
}
pub trait DapAdapterEvalHook: Debug + Send + 'static {
fn add_dap_hooks(self: Box<Self>, eval: &mut Evaluator<'_, '_, '_>);
}
pub fn dap_capabilities() -> Capabilities {
Capabilities {
supports_configuration_done_request: Some(true),
supports_evaluate_for_hovers: Some(true),
supports_set_variable: Some(true),
supports_step_in_targets_request: Some(true),
supports_conditional_breakpoints: Some(true),
..Capabilities::default()
}
}
pub fn prepare_dap_adapter(
client: Box<dyn DapAdapterClient>,
) -> (impl DapAdapter, impl DapAdapterEvalHook) {
implementation::prepare_dap_adapter(client)
}