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,73 +1,107 @@
use crate::tree_node::{Expression, Literal, Spanned};
use super::LiteralOrVariable; use super::LiteralOrVariable;
use crate::tree_node::{Expression, Literal, Spanned};
use crate::{Documentation, documented};
#[derive(Debug, PartialEq, Eq)] documented! {
pub enum Math { #[derive(Debug, PartialEq, Eq)]
/// Returns the angle in radians whose cosine is the specified number. pub enum Math {
/// ## In Game /// Returns the angle in radians whose cosine is the specified number.
/// `acos r? a(r?|num)` /// ## IC10
Acos(LiteralOrVariable), /// `acos r? a(r?|num)`
/// Returns the angle in radians whose sine is the specified number. /// ## Slang
/// ## In Game /// `(number|var).acos();`
/// `asin r? a(r?|num)` Acos(LiteralOrVariable),
Asin(LiteralOrVariable), /// Returns the angle in radians whose sine is the specified number.
/// Returns the angle in radians whose tangent is the specified number. /// ## IC10
/// ## In Game /// `asin r? a(r?|num)`
/// `atan r? a(r?|num)` /// ## Slang
Atan(LiteralOrVariable), /// `(number|var).asin();`
/// Returns the angle in radians whose tangent is the quotient of the specified numbers. Asin(LiteralOrVariable),
/// ## In Game /// Returns the angle in radians whose tangent is the specified number.
/// `atan2 r? a(r?|num) b(r?|num)` /// ## IC10
Atan2(LiteralOrVariable, LiteralOrVariable), /// `atan r? a(r?|num)`
/// Gets the absolute value of a number. /// ## Slang
/// ## In Game /// `(number|var).atan();`
/// `abs r? a(r?|num)` Atan(LiteralOrVariable),
Abs(LiteralOrVariable), /// Returns the angle in radians whose tangent is the quotient of the specified numbers.
/// Rounds a number up to the nearest whole number. /// ## IC10
/// ## In Game /// `atan2 r? a(r?|num) b(r?|num)`
/// `ceil r? a(r?|num)` /// ## Slang
Ceil(LiteralOrVariable), /// `(number|var).atan2((number|var));`
/// Returns the cosine of the specified angle in radians. Atan2(LiteralOrVariable, LiteralOrVariable),
/// ## In Game /// Gets the absolute value of a number.
/// cos r? a(r?|num) /// ## IC10
Cos(LiteralOrVariable), /// `abs r? a(r?|num)`
/// Rounds a number down to the nearest whole number. /// ## Slang
/// ## In Game /// `(number|var).abs();`
/// `floor r? a(r?|num)` Abs(LiteralOrVariable),
Floor(LiteralOrVariable), /// Rounds a number up to the nearest whole number.
/// Computes the natural logarithm of a number. /// ## IC10
/// ## In Game /// `ceil r? a(r?|num)`
/// `log r? a(r?|num)` /// ## Slang
Log(LiteralOrVariable), /// `(number|var).ceil();`
/// Computes the maximum of two numbers. Ceil(LiteralOrVariable),
/// ## In Game /// Returns the cosine of the specified angle in radians.
/// `max r? a(r?|num) b(r?|num)` /// ## IC10
Max(LiteralOrVariable, LiteralOrVariable), /// `cos r? a(r?|num)`
/// Computes the minimum of two numbers. /// ## Slang
/// ## In Game /// `(number|var).cos();`
/// `min r? a(r?|num) b(r?|num)` Cos(LiteralOrVariable),
Min(LiteralOrVariable, LiteralOrVariable), /// Rounds a number down to the nearest whole number.
/// Gets a random number between 0 and 1. /// ## In Game
/// ## In Game /// `floor r? a(r?|num)`
/// `rand r?` /// ## Slang
Rand, /// `(number|var).floor();`
/// Returns the sine of the specified angle in radians. Floor(LiteralOrVariable),
/// ## In Game /// Computes the natural logarithm of a number.
/// `sin r? a(r?|num)` /// ## IC10
Sin(LiteralOrVariable), /// `log r? a(r?|num)`
/// Computes the square root of a number. /// ## Slang
/// ## In Game /// `(number|var).log();`
/// `sqrt r? a(r?|num)` Log(LiteralOrVariable),
Sqrt(LiteralOrVariable), /// Computes the maximum of two numbers.
/// Returns the tangent of the specified angle in radians. /// ## IC10
/// ## In Game /// `max r? a(r?|num) b(r?|num)`
/// `tan r? a(r?|num)` /// ## Slang
Tan(LiteralOrVariable), /// `(number|var).max((number|var));`
/// Truncates a number by removing the decimal portion. Max(LiteralOrVariable, LiteralOrVariable),
/// ## In Game /// Computes the minimum of two numbers.
/// `trunc r? a(r?|num)` /// ## IC10
Trunc(LiteralOrVariable), /// `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.
/// ## IC10
/// `rand r?`
/// ## Slang
/// `rand();`
Rand,
/// Returns the sine of the specified angle in radians.
/// ## IC10
/// `sin r? a(r?|num)`
/// ## Slang
/// `(number|var).sin();`
Sin(LiteralOrVariable),
/// Computes the square root of a number.
/// ## IC10
/// `sqrt r? a(r?|num)`
/// ## Slang
/// `(number|var).sqrt();`
Sqrt(LiteralOrVariable),
/// Returns the tangent of the specified angle in radians.
/// ## IC10
/// `tan r? a(r?|num)`
/// ## Slang
/// `(number|var).tan();`
Tan(LiteralOrVariable),
/// Truncates a number by removing the decimal portion.
/// ## IC10
/// `trunc r? a(r?|num)`
/// ## Slang
/// `(number|var).trunc();`
Trunc(LiteralOrVariable),
}
} }
impl std::fmt::Display for Math { impl std::fmt::Display for Math {
@@ -93,71 +127,73 @@ impl std::fmt::Display for Math {
} }
} }
#[derive(Debug, PartialEq, Eq)] documented! {
pub enum System { #[derive(Debug, PartialEq, Eq)]
/// Pauses execution for exactly 1 tick and then resumes. pub enum System {
/// ## In Game /// Pauses execution for exactly 1 tick and then resumes.
/// yield /// ## In Game
Yield, /// yield
/// Represents a function that can be called to sleep for a certain amount of time. Yield,
/// ## In Game /// Represents a function that can be called to sleep for a certain amount of time.
/// `sleep a(r?|num)` /// ## In Game
Sleep(Box<Spanned<Expression>>), /// `sleep a(r?|num)`
/// Gets the in-game hash for a specific prefab name. Sleep(Box<Spanned<Expression>>),
/// ## In Game /// Gets the in-game hash for a specific prefab name.
/// `HASH("prefabName")` /// ## In Game
Hash(Literal), /// `HASH("prefabName")`
/// Represents a function which loads a device variable into a register. Hash(Literal),
/// ## In Game /// Represents a function which loads a device variable into a register.
/// `l r? d? var` /// ## In Game
/// ## Examples /// `l r? d? var`
/// `l r0 d0 Setting` /// ## Examples
/// `l r1 d5 Pressure` /// `l r0 d0 Setting`
LoadFromDevice(LiteralOrVariable, Literal), /// `l r1 d5 Pressure`
/// Function which gets a LogicType from all connected network devices that match LoadFromDevice(LiteralOrVariable, Literal),
/// the provided device hash and name, aggregating them via a batchMode /// Function which gets a LogicType from all connected network devices that match
/// ## In Game /// the provided device hash and name, aggregating them via a batchMode
/// lbn r? deviceHash nameHash logicType batchMode /// ## In Game
/// ## Examples /// lbn r? deviceHash nameHash logicType batchMode
/// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum /// ## Examples
LoadBatchNamed( /// lbn r0 HASH("StructureWallLight") HASH("wallLight") On Minimum
LiteralOrVariable, LoadBatchNamed(
Box<Spanned<Expression>>, LiteralOrVariable,
Literal, Box<Spanned<Expression>>,
Literal, Literal,
), Literal,
/// Loads a LogicType from all connected network devices, aggregating them via a ),
/// batchMode /// Loads a LogicType from all connected network devices, aggregating them via a
/// ## In Game /// batchMode
/// lb r? deviceHash logicType batchMode /// ## In Game
/// ## Examples /// lb r? deviceHash logicType batchMode
/// lb r0 HASH("StructureWallLight") On Minimum /// ## Examples
LoadBatch(LiteralOrVariable, Literal, Literal), /// lb r0 HASH("StructureWallLight") On Minimum
/// Represents a function which stores a setting into a specific device. LoadBatch(LiteralOrVariable, Literal, Literal),
/// ## In Game /// Represents a function which stores a setting into a specific device.
/// `s d? logicType r?` /// ## In Game
/// ## Example /// `s d? logicType r?`
/// `s d0 Setting r0` /// ## Example
SetOnDevice(LiteralOrVariable, Literal, Box<Spanned<Expression>>), /// `s d0 Setting r0`
/// Represents a function which stores a setting to all devices that match SetOnDevice(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
/// the given deviceHash /// Represents a function which stores a setting to all devices that match
/// ## In Game /// the given deviceHash
/// `sb deviceHash logicType r?` /// ## In Game
/// ## Example /// `sb deviceHash logicType r?`
/// `sb HASH("Doors") Lock 1` /// ## Example
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Spanned<Expression>>), /// `sb HASH("Doors") Lock 1`
/// Represents a function which stores a setting to all devices that match SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Spanned<Expression>>),
/// both the given deviceHash AND the given nameHash /// Represents a function which stores a setting to all devices that match
/// ## In Game /// both the given deviceHash AND the given nameHash
/// `sbn deviceHash nameHash logicType r?` /// ## In Game
/// ## Example /// `sbn deviceHash nameHash logicType r?`
/// `sbn HASH("Doors") HASH("Exterior") Lock 1` /// ## Example
SetOnDeviceBatchedNamed( /// `sbn HASH("Doors") HASH("Exterior") Lock 1`
LiteralOrVariable, SetOnDeviceBatchedNamed(
LiteralOrVariable, LiteralOrVariable,
Literal, LiteralOrVariable,
Box<Spanned<Expression>>, Literal,
), Box<Spanned<Expression>>,
),
}
} }
impl std::fmt::Display for System { 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 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(())