use std::collections::HashMap;
use crate::datum::{Datum, DatumKind};
use crate::options::Options;
use crate::reader::parse;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Role {
Keyword,
Name,
Arglist,
Docstring,
Declare,
Interactive,
Body,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Confidence {
Builtin,
Declared,
Inferred,
Weak,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FormSpec {
pub head: String,
pub leading: Vec<Role>,
pub docstring: bool,
pub body: bool,
pub confidence: Confidence,
}
impl FormSpec {
fn new(
head: impl Into<String>,
leading: Vec<Role>,
docstring: bool,
body: bool,
confidence: Confidence,
) -> Self {
FormSpec {
head: head.into(),
leading,
docstring,
body,
confidence,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct Registry {
specs: HashMap<String, FormSpec>,
}
impl Registry {
pub fn new() -> Self {
Registry::default()
}
pub fn insert(&mut self, spec: FormSpec) {
self.specs.insert(spec.head.clone(), spec);
}
pub fn get(&self, head: &str) -> Option<&FormSpec> {
self.specs.get(head)
}
pub fn len(&self) -> usize {
self.specs.len()
}
pub fn is_empty(&self) -> bool {
self.specs.is_empty()
}
}
#[derive(Debug)]
pub struct Part<'a, 't> {
pub role: Role,
pub datum: &'a Datum<'t>,
}
#[derive(Debug)]
pub struct Annotated<'a, 't> {
pub head: &'t str,
pub parts: Vec<Part<'a, 't>>,
pub confidence: Confidence,
}
impl<'a, 't> Annotated<'a, 't> {
pub fn first(&self, role: Role) -> Option<&'a Datum<'t>> {
self.parts.iter().find(|p| p.role == role).map(|p| p.datum)
}
}
fn list_head<'a, 't>(datum: &'a Datum<'t>) -> Option<(&'t str, &'a [Datum<'t>])> {
if let DatumKind::List { items, .. } = &datum.kind {
if let Some(first) = items.first() {
if let DatumKind::Symbol(s) = first.kind {
return Some((s, items));
}
}
}
None
}
pub fn annotate_form<'a, 't>(form: &'a Datum<'t>, reg: &Registry) -> Option<Annotated<'a, 't>> {
let (head, items) = list_head(form)?;
let spec = reg.get(head)?;
let mut parts = Vec::with_capacity(items.len());
parts.push(Part {
role: Role::Keyword,
datum: &items[0],
});
let mut i = 1;
for &role in &spec.leading {
if i >= items.len() {
break;
}
parts.push(Part {
role,
datum: &items[i],
});
i += 1;
}
if spec.docstring && i < items.len() {
if let DatumKind::Str(_) = items[i].kind {
if i + 1 < items.len() {
parts.push(Part {
role: Role::Docstring,
datum: &items[i],
});
i += 1;
}
}
}
if spec.body {
for item in &items[i..] {
let role = match list_head(item) {
Some(("declare", _)) => Role::Declare,
Some(("interactive", _)) => Role::Interactive,
_ => Role::Body,
};
parts.push(Part { role, datum: item });
}
}
Some(Annotated {
head,
parts,
confidence: spec.confidence,
})
}
pub fn annotate_tree<'a, 't>(data: &'a [Datum<'t>], reg: &Registry) -> Vec<Annotated<'a, 't>> {
let mut out = Vec::new();
for datum in data {
collect(datum, reg, &mut out);
}
out
}
fn collect<'a, 't>(datum: &'a Datum<'t>, reg: &Registry, out: &mut Vec<Annotated<'a, 't>>) {
if let Some(annotated) = annotate_form(datum, reg) {
out.push(annotated);
}
if let DatumKind::List { items, .. } = &datum.kind {
for item in items {
collect(item, reg, out);
}
}
}
fn strip_earmuffs(s: &str) -> &str {
s.trim_matches(|c| c == '*' || c == '_')
}
fn classify_param(name: &str) -> Option<Role> {
match strip_earmuffs(&name.to_ascii_lowercase()) {
"name" | "names" | "symbol" | "sym" | "fsym" | "fn-name" | "var" | "variable" | "place"
| "target" | "def" => Some(Role::Name),
"arglist" | "args" | "arguments" | "lambda-list" | "key-args" | "params" | "parameters"
| "ll" => Some(Role::Arglist),
"docstring" | "doc" | "doc-string" => Some(Role::Docstring),
"body" | "forms" | "bodyform" | "def-body" | "rest" | "heads" | "clauses" => {
Some(Role::Body)
}
_ => None,
}
}
pub fn harvest_source(source: &str, reg: &mut Registry) -> usize {
let parsed = parse(source, &Options::emacs_lisp());
let mut added = 0;
for datum in &parsed.data {
if let Some(spec) = harvest_defmacro(datum) {
reg.insert(spec);
added += 1;
}
}
added
}
fn harvest_defmacro(form: &Datum<'_>) -> Option<FormSpec> {
let (head, items) = list_head(form)?;
if head != "defmacro" && head != "cl-defmacro" {
return None;
}
let name = match items.get(1)?.kind {
DatumKind::Symbol(s) => s,
_ => return None,
};
let DatumKind::List { items: params, .. } = &items.get(2)?.kind else {
return None;
};
let mut leading = Vec::new();
let mut docstring = false;
let mut body = false;
let mut matched_any = false;
let mut rest = false;
for p in params {
let DatumKind::Symbol(pname) = p.kind else {
continue;
};
if pname == "&optional" {
continue;
}
if pname == "&rest" || pname == "&body" {
rest = true;
continue;
}
match classify_param(pname) {
Some(Role::Body) => {
body = true;
matched_any = true;
break;
}
Some(Role::Docstring) => {
docstring = true;
matched_any = true;
}
Some(role) => {
leading.push(role);
matched_any = true;
if rest {
body = true;
break;
}
}
None => leading.push(Role::Other),
}
if rest {
body = true;
break;
}
}
let mut declared = false;
for item in &items[3.min(items.len())..] {
if let Some(("declare", decl_items)) = list_head(item) {
for spec in &decl_items[1..] {
if let Some((key, _)) = list_head(spec) {
match key {
"doc-string" => {
docstring = true;
declared = true;
}
"debug" => declared = true, _ => {}
}
}
}
}
}
let confidence = if declared {
Confidence::Declared
} else if matched_any {
Confidence::Inferred
} else {
Confidence::Weak
};
Some(FormSpec::new(name, leading, docstring, body, confidence))
}
pub fn emacs_lisp_builtins() -> Registry {
use Role::{Arglist, Name};
let mut reg = Registry::new();
let mut def = |head: &str, leading: Vec<Role>, doc: bool, body: bool| {
reg.insert(FormSpec::new(head, leading, doc, body, Confidence::Builtin));
};
for head in [
"defun",
"defmacro",
"defsubst",
"cl-defun",
"cl-defmacro",
"cl-defsubst",
"cl-defgeneric",
"cl-defmethod",
"define-inline",
] {
def(head, vec![Name, Arglist], true, true);
}
for head in [
"defvar",
"defvar-local",
"defconst",
"defcustom",
"defface",
"defgroup",
"defvar-keymap",
] {
def(head, vec![Name], true, true);
}
for head in [
"define-minor-mode",
"define-derived-mode",
"define-global-minor-mode",
"cl-defstruct",
"ert-deftest",
] {
def(head, vec![Name], true, true);
}
reg
}