Start generating documentation for built-in types and functions

This commit is contained in:
2025-12-01 23:08:56 -07:00
parent 5de614cc38
commit b3c732bbb7
7 changed files with 279 additions and 138 deletions

View File

@@ -496,6 +496,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"lsp-types",
"pretty_assertions",
"quick-error",
"tokenizer",
]

View File

@@ -11,3 +11,4 @@ lsp-types = { workspace = true }
[dev-dependencies]
anyhow = { version = "1" }
pretty_assertions = "1.4"

View File

@@ -1,3 +1,4 @@
mod macros;
#[cfg(test)]
mod test;
@@ -14,6 +15,10 @@ use tokenizer::{
};
use tree_node::*;
pub trait Documentation {
fn docs(&self) -> String;
}
#[macro_export]
/// A macro to create a boxed value.
macro_rules! boxed {

View File

@@ -0,0 +1,85 @@
#[macro_export]
macro_rules! documented {
// -------------------------------------------------------------------------
// Internal Helper: Filter doc comments
// -------------------------------------------------------------------------
// Case 1: Doc comment. Return Some("string").
// We match the specific structure of a doc attribute.
(@doc_filter #[doc = $doc:expr]) => {
Some($doc)
};
// Case 2: Other attributes (derives, etc.). Return None.
// We catch any other token sequence inside the brackets.
(@doc_filter #[$($attr:tt)*]) => {
None
};
// -------------------------------------------------------------------------
// Internal Helper: Match patterns for `match self`
// -------------------------------------------------------------------------
(@arm $name:ident $variant:ident) => {
$name::$variant
};
(@arm $name:ident $variant:ident ( $($tuple:tt)* )) => {
$name::$variant(..)
};
(@arm $name:ident $variant:ident { $($structure:tt)* }) => {
$name::$variant{..}
};
// -------------------------------------------------------------------------
// Main Macro Entry Point
// -------------------------------------------------------------------------
(
$(#[$enum_attr:meta])* $vis:vis enum $name:ident {
$(
// Capture attributes as a sequence of token trees inside brackets
// to avoid "local ambiguity" and handle multi-token attributes (like doc="...").
$(#[ $($variant_attr:tt)* ])*
$variant:ident
$( ($($tuple:tt)*) )?
$( {$($structure:tt)*} )?
),* $(,)?
}
) => {
// 1. Generate the actual Enum definition
$(#[$enum_attr])*
$vis enum $name {
$(
$(#[ $($variant_attr)* ])*
$variant
$( ($($tuple)*) )?
$( {$($structure)*} )?,
)*
}
// 2. Implement the Trait
impl Documentation for $name {
fn docs(&self) -> String {
match self {
$(
documented!(@arm $name $variant $( ($($tuple)*) )? $( {$($structure)*} )? ) => {
// Create a temporary array of Option<&str> for all attributes
let doc_lines: &[Option<&str>] = &[
$(
documented!(@doc_filter #[ $($variant_attr)* ])
),*
];
// Filter out the Nones (non-doc attributes), join, and return
doc_lines.iter()
.filter_map(|&d| d)
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_string()
}
)*
}
}
}
};
}

View File

@@ -1,73 +1,107 @@
use crate::tree_node::{Expression, Literal, Spanned};
use super::LiteralOrVariable;
use crate::tree_node::{Expression, Literal, Spanned};
use crate::{Documentation, documented};
#[derive(Debug, PartialEq, Eq)]
pub enum Math {
documented! {
#[derive(Debug, PartialEq, Eq)]
pub enum Math {
/// Returns the angle in radians whose cosine is the specified number.
/// ## In Game
/// ## IC10
/// `acos r? a(r?|num)`
/// ## Slang
/// `(number|var).acos();`
Acos(LiteralOrVariable),
/// Returns the angle in radians whose sine is the specified number.
/// ## In Game
/// ## IC10
/// `asin r? a(r?|num)`
/// ## Slang
/// `(number|var).asin();`
Asin(LiteralOrVariable),
/// Returns the angle in radians whose tangent is the specified number.
/// ## In Game
/// ## IC10
/// `atan r? a(r?|num)`
/// ## Slang
/// `(number|var).atan();`
Atan(LiteralOrVariable),
/// Returns the angle in radians whose tangent is the quotient of the specified numbers.
/// ## In Game
/// ## IC10
/// `atan2 r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).atan2((number|var));`
Atan2(LiteralOrVariable, LiteralOrVariable),
/// Gets the absolute value of a number.
/// ## In Game
/// ## IC10
/// `abs r? a(r?|num)`
/// ## Slang
/// `(number|var).abs();`
Abs(LiteralOrVariable),
/// Rounds a number up to the nearest whole number.
/// ## In Game
/// ## IC10
/// `ceil r? a(r?|num)`
/// ## Slang
/// `(number|var).ceil();`
Ceil(LiteralOrVariable),
/// Returns the cosine of the specified angle in radians.
/// ## In Game
/// cos r? a(r?|num)
/// ## IC10
/// `cos r? a(r?|num)`
/// ## Slang
/// `(number|var).cos();`
Cos(LiteralOrVariable),
/// Rounds a number down to the nearest whole number.
/// ## In Game
/// `floor r? a(r?|num)`
/// ## Slang
/// `(number|var).floor();`
Floor(LiteralOrVariable),
/// Computes the natural logarithm of a number.
/// ## In Game
/// ## IC10
/// `log r? a(r?|num)`
/// ## Slang
/// `(number|var).log();`
Log(LiteralOrVariable),
/// Computes the maximum of two numbers.
/// ## In Game
/// ## IC10
/// `max r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).max((number|var));`
Max(LiteralOrVariable, LiteralOrVariable),
/// Computes the minimum of two numbers.
/// ## In Game
/// ## IC10
/// `min r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).min((number|var));`
Min(LiteralOrVariable, LiteralOrVariable),
/// Gets a random number between 0 and 1.
/// ## In Game
/// ## IC10
/// `rand r?`
/// ## Slang
/// `rand();`
Rand,
/// Returns the sine of the specified angle in radians.
/// ## In Game
/// ## IC10
/// `sin r? a(r?|num)`
/// ## Slang
/// `(number|var).sin();`
Sin(LiteralOrVariable),
/// Computes the square root of a number.
/// ## In Game
/// ## IC10
/// `sqrt r? a(r?|num)`
/// ## Slang
/// `(number|var).sqrt();`
Sqrt(LiteralOrVariable),
/// Returns the tangent of the specified angle in radians.
/// ## In Game
/// ## IC10
/// `tan r? a(r?|num)`
/// ## Slang
/// `(number|var).tan();`
Tan(LiteralOrVariable),
/// Truncates a number by removing the decimal portion.
/// ## In Game
/// ## IC10
/// `trunc r? a(r?|num)`
/// ## Slang
/// `(number|var).trunc();`
Trunc(LiteralOrVariable),
}
}
impl std::fmt::Display for Math {
@@ -93,8 +127,9 @@ impl std::fmt::Display for Math {
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum System {
documented! {
#[derive(Debug, PartialEq, Eq)]
pub enum System {
/// Pauses execution for exactly 1 tick and then resumes.
/// ## In Game
/// yield
@@ -158,6 +193,7 @@ pub enum System {
Literal,
Box<Spanned<Expression>>,
),
}
}
impl std::fmt::Display for System {
@@ -229,4 +265,3 @@ impl SysCall {
)
}
}

View File

@@ -0,0 +1,12 @@
use crate::Documentation;
use crate::sys_call;
use pretty_assertions::assert_eq;
#[test]
fn test_token_tree_docs() -> anyhow::Result<()> {
let syscall = sys_call::System::Yield;
assert_eq!(syscall.docs(), "");
Ok(())
}

View File

@@ -6,9 +6,11 @@ macro_rules! parser {
}
mod blocks;
mod docs;
use super::Parser;
use super::Tokenizer;
use anyhow::Result;
use pretty_assertions::assert_eq;
#[test]
fn test_unsupported_keywords() -> Result<()> {
@@ -99,16 +101,16 @@ fn test_priority_expression() -> Result<()> {
#[test]
fn test_binary_expression() -> Result<()> {
let expr = parser!("4 ** 2 + 5 ** 2").parse()?.unwrap();
let expr = parser!("4 ** 2 + 5 ** 2;").parse()?.unwrap();
assert_eq!("((4 ** 2) + (5 ** 2))", expr.to_string());
let expr = parser!("2 ** 3 ** 4").parse()?.unwrap();
let expr = parser!("2 ** 3 ** 4;").parse()?.unwrap();
assert_eq!("(2 ** (3 ** 4))", expr.to_string());
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2").parse()?.unwrap();
let expr = parser!("45 * 2 - 15 / 5 + 5 ** 2;").parse()?.unwrap();
assert_eq!("(((45 * 2) - (15 / 5)) + (5 ** 2))", expr.to_string());
let expr = parser!("(5 - 2) * 10").parse()?.unwrap();
let expr = parser!("(5 - 2) * 10;").parse()?.unwrap();
assert_eq!("((5 - 2) * 10)", expr.to_string());
Ok(())