use std::collections::HashMap;
use crate::datum::{Datum, DatumKind, Delim};
use crate::options::{Dialect, Options};
use crate::reader::parse;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Role {
Keyword,
Name,
Qualifier,
DispatchValue,
SpecializedArglist,
Arglist,
Docstring,
Declare,
Interactive,
Body,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Category {
Function,
Macro,
Variable,
Constant,
Class,
Struct,
Generic,
Method,
Type,
Test,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Confidence {
Builtin,
Declared,
Inferred,
Weak,
Consumer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Dispatch {
Qualifiers,
Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Docstring {
None,
Leading,
LeadingOrLone,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FormSpec {
pub head: String,
pub leading: Vec<Role>,
pub docstring: Docstring,
pub body: bool,
pub category: Option<Category>,
pub dispatch: Option<Dispatch>,
pub confidence: Confidence,
}
impl FormSpec {
fn new(
head: impl Into<String>,
leading: Vec<Role>,
docstring: Docstring,
body: bool,
confidence: Confidence,
) -> Self {
FormSpec {
head: head.into(),
leading,
docstring,
body,
category: None,
dispatch: None,
confidence,
}
}
pub fn define(
head: impl Into<String>,
leading: Vec<Role>,
docstring: Docstring,
body: bool,
) -> Self {
FormSpec::new(head, leading, docstring, body, Confidence::Consumer)
}
pub fn with_category(mut self, category: Category) -> Self {
self.category = Some(category);
self
}
pub fn with_dispatch(mut self, dispatch: Dispatch) -> Self {
self.dispatch = Some(dispatch);
self
}
}
#[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 remove(&mut self, head: &str) -> Option<FormSpec> {
self.specs.remove(head)
}
pub fn iter(&self) -> impl Iterator<Item = &FormSpec> {
self.specs.values()
}
pub fn merge(&mut self, other: Registry) {
self.specs.extend(other.specs);
}
pub fn len(&self) -> usize {
self.specs.len()
}
pub fn is_empty(&self) -> bool {
self.specs.is_empty()
}
}
impl Extend<FormSpec> for Registry {
fn extend<T: IntoIterator<Item = FormSpec>>(&mut self, iter: T) {
for spec in iter {
self.insert(spec);
}
}
}
impl FromIterator<FormSpec> for Registry {
fn from_iter<T: IntoIterator<Item = FormSpec>>(iter: T) -> Self {
let mut reg = Registry::new();
reg.extend(iter);
reg
}
}
#[derive(Debug)]
pub struct Part<'a, 't> {
pub role: Role,
pub datum: &'a Datum<'t>,
}
#[derive(Debug)]
pub struct Annotated<'a, 't> {
pub form: &'a Datum<'t>,
pub head: &'t str,
pub parts: Vec<Part<'a, 't>>,
pub category: Option<Category>,
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)
}
pub fn all(&self, role: Role) -> impl Iterator<Item = &'a Datum<'t>> + '_ {
self.parts
.iter()
.filter(move |p| p.role == role)
.map(|p| p.datum)
}
pub fn specialized_params(&self) -> Vec<SpecializedParam<'a, 't>> {
match self.first(Role::SpecializedArglist) {
Some(arglist) => split_specialized_arglist(arglist),
None => Vec::new(),
}
}
}
#[derive(Debug)]
pub struct SpecializedParam<'a, 't> {
pub variable: &'a Datum<'t>,
pub specializer: Option<&'a Datum<'t>>,
}
pub fn split_specialized_arglist<'a, 't>(arglist: &'a Datum<'t>) -> Vec<SpecializedParam<'a, 't>> {
let DatumKind::List { items, .. } = &arglist.kind else {
return Vec::new();
};
let mut out = Vec::new();
for item in items {
match &item.kind {
DatumKind::Symbol(s) if s.starts_with('&') => break,
DatumKind::Symbol(_) => out.push(SpecializedParam {
variable: item,
specializer: None,
}),
DatumKind::List { items: pair, .. } => {
if let Some(var) = pair.first() {
out.push(SpecializedParam {
variable: var,
specializer: pair.get(1),
});
}
}
_ => {}
}
}
out
}
fn is_delimited_list(datum: &Datum<'_>) -> bool {
matches!(datum.kind, DatumKind::List { .. })
}
fn list_head<'a, 't>(datum: &'a Datum<'t>) -> Option<(&'t str, &'a [Datum<'t>])> {
let items = datum.items()?;
let head = datum.head_symbol()?;
Some((head, items))
}
#[must_use]
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;
}
if role == Role::Name && !is_name_shaped(&items[i]) {
return None;
}
parts.push(Part {
role,
datum: &items[i],
});
i += 1;
}
match spec.dispatch {
Some(Dispatch::Qualifiers) => {
while i < items.len() && !is_delimited_list(&items[i]) {
parts.push(Part {
role: Role::Qualifier,
datum: &items[i],
});
i += 1;
}
if i < items.len() && is_delimited_list(&items[i]) {
parts.push(Part {
role: Role::SpecializedArglist,
datum: &items[i],
});
i += 1;
}
}
Some(Dispatch::Value) => {
if i < items.len() {
parts.push(Part {
role: Role::DispatchValue,
datum: &items[i],
});
i += 1;
}
if i < items.len()
&& matches!(
items[i].kind,
DatumKind::List {
delim: Delim::Square,
..
}
)
{
parts.push(Part {
role: Role::Arglist,
datum: &items[i],
});
i += 1;
}
}
None => {}
}
if i < items.len() {
let is_str = matches!(items[i].kind, DatumKind::Str(_));
let accepted = match spec.docstring {
Docstring::None => false,
Docstring::Leading => is_str && i + 1 < items.len(),
Docstring::LeadingOrLone => is_str,
};
if accepted {
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 {
form,
head,
parts,
category: spec.category,
confidence: spec.confidence,
})
}
fn is_name_shaped(datum: &Datum<'_>) -> bool {
match &datum.kind {
DatumKind::Symbol(_) => true,
DatumKind::List {
delim: Delim::Round,
items,
..
} => matches!(items.first().map(|d| &d.kind), Some(DatumKind::Symbol(_))),
_ => false,
}
}
#[must_use]
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;
}
if rest {
if classify_param(pname).is_some() {
matched_any = true;
}
body = true;
break;
}
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;
}
None => leading.push(Role::Other),
}
}
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
};
let docstring = if docstring {
Docstring::LeadingOrLone
} else {
Docstring::None
};
Some(FormSpec::new(name, leading, docstring, body, confidence))
}
struct Builtins {
reg: Registry,
}
impl Builtins {
fn new() -> Self {
Builtins {
reg: Registry::new(),
}
}
fn def(
&mut self,
head: &str,
leading: Vec<Role>,
doc: Docstring,
body: bool,
category: Option<Category>,
) {
let mut spec = FormSpec::new(head, leading, doc, body, Confidence::Builtin);
spec.category = category;
self.reg.insert(spec);
}
fn method(&mut self, head: &str, dispatch: Dispatch, doc: Docstring, category: Category) {
let mut spec = FormSpec::new(head, vec![Role::Name], doc, true, Confidence::Builtin);
spec.category = Some(category);
spec.dispatch = Some(dispatch);
self.reg.insert(spec);
}
}
#[must_use]
pub fn bundled_registry(dialect: Dialect) -> Registry {
match dialect {
Dialect::Scheme
| Dialect::Guile
| Dialect::Gauche
| Dialect::Mosh
| Dialect::Gambit
| Dialect::SchemeSuperset => scheme_builtins(),
Dialect::Racket => racket_builtins(),
Dialect::CommonLisp => common_lisp_builtins(),
Dialect::EmacsLisp => emacs_lisp_builtins(),
Dialect::Clojure => clojure_builtins(),
Dialect::Phel => clojure_builtins(),
Dialect::Fennel => fennel_builtins(),
Dialect::Janet => janet_builtins(),
Dialect::Hy => hy_builtins(),
Dialect::Lfe => lfe_builtins(),
Dialect::Islisp => islisp_builtins(),
Dialect::AutoLisp => autolisp_builtins(),
Dialect::Edn => Registry::new(),
}
}
fn emacs_lisp_builtins() -> Registry {
use Category::{Constant, Function, Generic, Macro, Method, Struct, Test, Variable};
use Docstring::{LeadingOrLone, None as NoDoc};
use Role::{Arglist, Name, Other};
let mut b = Builtins::new();
let fnlike = vec![Name, Arglist];
b.def("defun", fnlike.clone(), LeadingOrLone, true, Some(Function));
b.def(
"defsubst",
fnlike.clone(),
LeadingOrLone,
true,
Some(Function),
);
b.def(
"cl-defun",
fnlike.clone(),
LeadingOrLone,
true,
Some(Function),
);
b.def(
"cl-defsubst",
fnlike.clone(),
LeadingOrLone,
true,
Some(Function),
);
b.def(
"define-inline",
fnlike.clone(),
LeadingOrLone,
true,
Some(Function),
);
b.def("defmacro", fnlike.clone(), LeadingOrLone, true, Some(Macro));
b.def(
"cl-defmacro",
fnlike.clone(),
LeadingOrLone,
true,
Some(Macro),
);
b.def("cl-defgeneric", fnlike, LeadingOrLone, true, Some(Generic));
b.method("cl-defmethod", Dispatch::Qualifiers, LeadingOrLone, Method);
let varlike = vec![Name, Other];
b.def(
"defvar",
varlike.clone(),
LeadingOrLone,
false,
Some(Variable),
);
b.def(
"defvar-local",
varlike.clone(),
LeadingOrLone,
false,
Some(Variable),
);
b.def(
"defconst",
varlike.clone(),
LeadingOrLone,
false,
Some(Constant),
);
b.def(
"defcustom",
varlike.clone(),
LeadingOrLone,
true,
Some(Variable),
);
b.def(
"defface",
varlike.clone(),
LeadingOrLone,
true,
Some(Variable),
);
b.def("defgroup", varlike, LeadingOrLone, true, None);
b.def("defvar-keymap", vec![Name], NoDoc, true, Some(Variable));
b.def("define-minor-mode", vec![Name], LeadingOrLone, true, None);
b.def(
"define-derived-mode",
vec![Name, Other, Other],
LeadingOrLone,
true,
None,
);
b.def(
"define-global-minor-mode",
vec![Name],
LeadingOrLone,
true,
None,
);
b.def(
"cl-defstruct",
vec![Name],
LeadingOrLone,
true,
Some(Struct),
);
b.def(
"ert-deftest",
vec![Name, Arglist],
LeadingOrLone,
true,
Some(Test),
);
b.reg
}
fn scheme_builtins() -> Registry {
use Category::{Macro, Type};
use Docstring::None as NoDoc;
use Role::Name;
let mut b = Builtins::new();
b.def("define", vec![Name], NoDoc, true, None);
b.def("define-values", vec![Name], NoDoc, true, None);
b.def("define-syntax", vec![Name], NoDoc, true, Some(Macro));
b.def("define-record-type", vec![Name], NoDoc, true, Some(Type));
b.reg
}
fn racket_builtins() -> Registry {
let mut reg = scheme_builtins();
reg.insert(
FormSpec::new(
"struct",
vec![Role::Name],
Docstring::None,
true,
Confidence::Builtin,
)
.with_category(Category::Struct),
);
reg
}
fn common_lisp_builtins() -> Registry {
use Category::{Class, Constant, Function, Generic, Macro, Method, Struct, Variable};
use Docstring::{Leading, LeadingOrLone, None as NoDoc};
use Role::{Arglist, Name, Other};
let mut b = Builtins::new();
b.def("defun", vec![Name, Arglist], Leading, true, Some(Function));
b.def("defmacro", vec![Name, Arglist], Leading, true, Some(Macro));
b.def(
"defgeneric",
vec![Name, Arglist],
NoDoc,
true,
Some(Generic),
);
b.method("defmethod", Dispatch::Qualifiers, Leading, Method);
b.def(
"defvar",
vec![Name, Other],
LeadingOrLone,
false,
Some(Variable),
);
b.def(
"defparameter",
vec![Name, Other],
LeadingOrLone,
false,
Some(Variable),
);
b.def(
"defconstant",
vec![Name, Other],
LeadingOrLone,
false,
Some(Constant),
);
b.def("defclass", vec![Name], NoDoc, true, Some(Class));
b.def("define-condition", vec![Name], NoDoc, true, Some(Class));
b.def("defstruct", vec![Name], Leading, true, Some(Struct));
b.def("defpackage", vec![Name], NoDoc, true, None);
b.reg
}
fn clojure_builtins() -> Registry {
use Category::{Function, Generic, Macro, Method, Struct, Test, Type};
use Docstring::{Leading, None as NoDoc};
use Role::Name;
let mut b = Builtins::new();
b.def("defn", vec![Name], Leading, true, Some(Function));
b.def("defn-", vec![Name], Leading, true, Some(Function));
b.def("defmacro", vec![Name], Leading, true, Some(Macro));
b.def("def", vec![Name], Leading, true, None); b.def("defonce", vec![Name], NoDoc, true, None);
b.def("defmulti", vec![Name], Leading, true, Some(Generic));
b.method("defmethod", Dispatch::Value, NoDoc, Method);
b.def("defprotocol", vec![Name], Leading, true, Some(Generic));
b.def("definterface", vec![Name], NoDoc, true, Some(Type));
b.def("defrecord", vec![Name], NoDoc, true, Some(Struct));
b.def("deftype", vec![Name], NoDoc, true, None);
b.def("deftest", vec![Name], NoDoc, true, Some(Test));
b.def("ns", vec![Name], Leading, true, None);
b.reg
}
fn fennel_builtins() -> Registry {
use Category::{Function, Macro, Variable};
use Docstring::{Leading, None as NoDoc};
use Role::{Arglist, Name};
let mut b = Builtins::new();
b.def("fn", vec![Name, Arglist], Leading, true, Some(Function));
b.def("lambda", vec![Name, Arglist], Leading, true, Some(Function));
b.def("λ", vec![Name, Arglist], Leading, true, Some(Function));
b.def("macro", vec![Name, Arglist], Leading, true, Some(Macro));
b.def("macros", vec![Name], NoDoc, true, Some(Macro));
b.def("local", vec![Name], NoDoc, true, Some(Variable));
b.def("var", vec![Name], NoDoc, true, Some(Variable));
b.def("global", vec![Name], NoDoc, true, Some(Variable));
b.reg
}
fn janet_builtins() -> Registry {
use Category::{Function, Macro, Variable};
use Docstring::Leading;
use Role::Name;
let mut b = Builtins::new();
b.def("defn", vec![Name], Leading, true, Some(Function));
b.def("defn-", vec![Name], Leading, true, Some(Function));
b.def("defmacro", vec![Name], Leading, true, Some(Macro));
b.def("defmacro-", vec![Name], Leading, true, Some(Macro));
b.def("def", vec![Name], Leading, true, None);
b.def("def-", vec![Name], Leading, true, None);
b.def("var", vec![Name], Leading, true, Some(Variable));
b.def("var-", vec![Name], Leading, true, Some(Variable));
b.reg
}
fn hy_builtins() -> Registry {
use Category::{Class, Function, Macro};
use Docstring::{LeadingOrLone, None as NoDoc};
use Role::{Arglist, Name};
let mut b = Builtins::new();
b.def(
"defn",
vec![Name, Arglist],
LeadingOrLone,
true,
Some(Function),
);
b.def(
"defmacro",
vec![Name, Arglist],
LeadingOrLone,
true,
Some(Macro),
);
b.def("defclass", vec![Name], LeadingOrLone, true, Some(Class));
b.def("setv", vec![Name], NoDoc, true, None);
b.reg
}
fn lfe_builtins() -> Registry {
use Category::{Function, Macro, Struct};
use Docstring::{Leading, None as NoDoc};
use Role::{Arglist, Name};
let mut b = Builtins::new();
b.def("defun", vec![Name, Arglist], Leading, true, Some(Function));
b.def("defmacro", vec![Name, Arglist], Leading, true, Some(Macro));
b.def("defrecord", vec![Name], NoDoc, true, Some(Struct));
b.def("defmodule", vec![Name], NoDoc, true, None);
b.reg
}
fn islisp_builtins() -> Registry {
use Category::{Class, Constant, Function, Generic, Macro, Method, Variable};
use Docstring::None as NoDoc;
use Role::{Arglist, Name};
let mut b = Builtins::new();
b.def("defun", vec![Name, Arglist], NoDoc, true, Some(Function));
b.def("defmacro", vec![Name, Arglist], NoDoc, true, Some(Macro));
b.def(
"defgeneric",
vec![Name, Arglist],
NoDoc,
true,
Some(Generic),
);
b.method("defmethod", Dispatch::Qualifiers, NoDoc, Method);
b.def("defclass", vec![Name], NoDoc, true, Some(Class));
b.def("defconstant", vec![Name], NoDoc, true, Some(Constant));
b.def("defglobal", vec![Name], NoDoc, true, Some(Variable));
b.def("defdynamic", vec![Name], NoDoc, true, Some(Variable));
b.reg
}
fn autolisp_builtins() -> Registry {
use Role::{Arglist, Name};
let mut b = Builtins::new();
b.def(
"defun",
vec![Name, Arglist],
Docstring::None,
true,
Some(Category::Function),
);
b.reg
}