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 = [ dependencies = [
"anyhow", "anyhow",
"lsp-types", "lsp-types",
"pretty_assertions",
"quick-error", "quick-error",
"tokenizer", "tokenizer",
] ]

View File

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

View File

@@ -1,3 +1,4 @@
mod macros;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
@@ -14,6 +15,10 @@ use tokenizer::{
}; };
use tree_node::*; use tree_node::*;
pub trait Documentation {
fn docs(&self) -> String;
}
#[macro_export] #[macro_export]
/// A macro to create a boxed value. /// A macro to create a boxed value.
macro_rules! boxed { 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,74 +1,108 @@
use crate::tree_node::{Expression, Literal, Spanned};
use super::LiteralOrVariable; use super::LiteralOrVariable;
use crate::tree_node::{Expression, Literal, Spanned};
use crate::{Documentation, documented};
documented! {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Math { pub enum Math {
/// Returns the angle in radians whose cosine is the specified number. /// Returns the angle in radians whose cosine is the specified number.
/// ## In Game /// ## IC10
/// `acos r? a(r?|num)` /// `acos r? a(r?|num)`
/// ## Slang
/// `(number|var).acos();`
Acos(LiteralOrVariable), Acos(LiteralOrVariable),
/// Returns the angle in radians whose sine is the specified number. /// Returns the angle in radians whose sine is the specified number.
/// ## In Game /// ## IC10
/// `asin r? a(r?|num)` /// `asin r? a(r?|num)`
/// ## Slang
/// `(number|var).asin();`
Asin(LiteralOrVariable), Asin(LiteralOrVariable),
/// Returns the angle in radians whose tangent is the specified number. /// Returns the angle in radians whose tangent is the specified number.
/// ## In Game /// ## IC10
/// `atan r? a(r?|num)` /// `atan r? a(r?|num)`
/// ## Slang
/// `(number|var).atan();`
Atan(LiteralOrVariable), Atan(LiteralOrVariable),
/// Returns the angle in radians whose tangent is the quotient of the specified numbers. /// 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)` /// `atan2 r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).atan2((number|var));`
Atan2(LiteralOrVariable, LiteralOrVariable), Atan2(LiteralOrVariable, LiteralOrVariable),
/// Gets the absolute value of a number. /// Gets the absolute value of a number.
/// ## In Game /// ## IC10
/// `abs r? a(r?|num)` /// `abs r? a(r?|num)`
/// ## Slang
/// `(number|var).abs();`
Abs(LiteralOrVariable), Abs(LiteralOrVariable),
/// Rounds a number up to the nearest whole number. /// Rounds a number up to the nearest whole number.
/// ## In Game /// ## IC10
/// `ceil r? a(r?|num)` /// `ceil r? a(r?|num)`
/// ## Slang
/// `(number|var).ceil();`
Ceil(LiteralOrVariable), Ceil(LiteralOrVariable),
/// Returns the cosine of the specified angle in radians. /// Returns the cosine of the specified angle in radians.
/// ## In Game /// ## IC10
/// cos r? a(r?|num) /// `cos r? a(r?|num)`
/// ## Slang
/// `(number|var).cos();`
Cos(LiteralOrVariable), Cos(LiteralOrVariable),
/// Rounds a number down to the nearest whole number. /// Rounds a number down to the nearest whole number.
/// ## In Game /// ## In Game
/// `floor r? a(r?|num)` /// `floor r? a(r?|num)`
/// ## Slang
/// `(number|var).floor();`
Floor(LiteralOrVariable), Floor(LiteralOrVariable),
/// Computes the natural logarithm of a number. /// Computes the natural logarithm of a number.
/// ## In Game /// ## IC10
/// `log r? a(r?|num)` /// `log r? a(r?|num)`
/// ## Slang
/// `(number|var).log();`
Log(LiteralOrVariable), Log(LiteralOrVariable),
/// Computes the maximum of two numbers. /// Computes the maximum of two numbers.
/// ## In Game /// ## IC10
/// `max r? a(r?|num) b(r?|num)` /// `max r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).max((number|var));`
Max(LiteralOrVariable, LiteralOrVariable), Max(LiteralOrVariable, LiteralOrVariable),
/// Computes the minimum of two numbers. /// Computes the minimum of two numbers.
/// ## In Game /// ## IC10
/// `min r? a(r?|num) b(r?|num)` /// `min r? a(r?|num) b(r?|num)`
/// ## Slang
/// `(number|var).min((number|var));`
Min(LiteralOrVariable, LiteralOrVariable), Min(LiteralOrVariable, LiteralOrVariable),
/// Gets a random number between 0 and 1. /// Gets a random number between 0 and 1.
/// ## In Game /// ## IC10
/// `rand r?` /// `rand r?`
/// ## Slang
/// `rand();`
Rand, Rand,
/// Returns the sine of the specified angle in radians. /// Returns the sine of the specified angle in radians.
/// ## In Game /// ## IC10
/// `sin r? a(r?|num)` /// `sin r? a(r?|num)`
/// ## Slang
/// `(number|var).sin();`
Sin(LiteralOrVariable), Sin(LiteralOrVariable),
/// Computes the square root of a number. /// Computes the square root of a number.
/// ## In Game /// ## IC10
/// `sqrt r? a(r?|num)` /// `sqrt r? a(r?|num)`
/// ## Slang
/// `(number|var).sqrt();`
Sqrt(LiteralOrVariable), Sqrt(LiteralOrVariable),
/// Returns the tangent of the specified angle in radians. /// Returns the tangent of the specified angle in radians.
/// ## In Game /// ## IC10
/// `tan r? a(r?|num)` /// `tan r? a(r?|num)`
/// ## Slang
/// `(number|var).tan();`
Tan(LiteralOrVariable), Tan(LiteralOrVariable),
/// Truncates a number by removing the decimal portion. /// Truncates a number by removing the decimal portion.
/// ## In Game /// ## IC10
/// `trunc r? a(r?|num)` /// `trunc r? a(r?|num)`
/// ## Slang
/// `(number|var).trunc();`
Trunc(LiteralOrVariable), Trunc(LiteralOrVariable),
} }
}
impl std::fmt::Display for Math { impl std::fmt::Display for Math {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -93,6 +127,7 @@ impl std::fmt::Display for Math {
} }
} }
documented! {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum System { pub enum System {
/// Pauses execution for exactly 1 tick and then resumes. /// Pauses execution for exactly 1 tick and then resumes.
@@ -159,6 +194,7 @@ pub enum System {
Box<Spanned<Expression>>, Box<Spanned<Expression>>,
), ),
} }
}
impl std::fmt::Display for System { impl std::fmt::Display for System {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -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 blocks;
mod docs;
use super::Parser; use super::Parser;
use super::Tokenizer; use super::Tokenizer;
use anyhow::Result; use anyhow::Result;
use pretty_assertions::assert_eq;
#[test] #[test]
fn test_unsupported_keywords() -> Result<()> { fn test_unsupported_keywords() -> Result<()> {
@@ -99,16 +101,16 @@ fn test_priority_expression() -> Result<()> {
#[test] #[test]
fn test_binary_expression() -> Result<()> { 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()); 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()); 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()); 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()); assert_eq!("((5 - 2) * 10)", expr.to_string());
Ok(()) Ok(())