ad_astra/
lib.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35//TODO check warnings regularly
36#![allow(warnings)]
37
38//! # Ad Astra API Documentation
39//!
40//! Ad Astra is a configurable scripting language platform designed for
41//! embedding in Rust applications.
42//!
43//! This documentation provides formal API descriptions. For a general
44//! exploration of the Ad Astra language and API usage, please refer to
45//! [The Ad Astra Book](https://ad-astra.lakhin.com/).
46//!
47//! Getting started examples are available in the
48//! [GitHub repository](https://github.com/Eliah-Lakhin/ad-astra/tree/master/work/examples).
49//!
50//! ## Requirements
51//!
52//! ### Desktop Builds
53//!
54//! For desktop builds of the script engine, the engine uses link sections to
55//! export introspection metadata of the exported Rust code into the
56//! script environment.
57//!
58//! This currently only works with the LLD linker, which is the default in
59//! stable Rust releases but optional in unstable releases. For the unstable
60//! Rust compiler, you need to reconfigure the linker manually.
61//!
62//! One way to configure the linker is to add the following configuration to
63//! the `.cargo/config.toml` file of your Rust project:
64//!
65//! ```toml
66//! [target.x86_64-unknown-linux-gnu]
67//! rustflags=["-Zlinker-features=-lld"]
68//! rustdocflags=["-Zlinker-features=-lld"]
69//! ```
70//!
71//! ### WebAssembly Builds
72//!
73//! The Ad Astra crate supports the WebAssembly build target, but the linker is
74//! not available for `wasm32-unknown-unknown` builds. To work around this
75//! issue, the exporting system generates "hidden" registration functions that
76//! start with the `__ADASTRA_EXPORT_` prefix.
77//!
78//! You need to manually call these functions before using the loaded wasm
79//! module in JavaScript:
80//!
81//! ```javascript
82//! // Loading a WebAssembly file.
83//! const assembly = fetch('./wasm.wasm');
84//!
85//! // Compiling the file in the browser.
86//! WebAssembly.instantiateStreaming(assembly, IMPORTS).then(({instance}) => {
87//!     // Calling each module-exported function that starts with the special
88//!     // `__ADASTRA_EXPORT_` prefix.
89//!     //
90//!     // These functions are generated by the Export macro.
91//!     // By invoking them manually, you register the corresponding item's
92//!     // introspection metadata in the Ad Astra script engine's export registry.
93//!     for (const property in instance.exports) {
94//!         if (property.startsWith('__ADASTRA_EXPORT_')) {
95//!             instance.exports[property]();
96//!         }
97//!     }
98//!
99//!     // The module is now ready for use.
100//! });
101//! ```
102//!
103//! ## Feature Flags
104//!
105//! - `export` flag: Enabled by default. Disabling this flag prevents the
106//!   generation of output code by the `#[export]` attribute macro.
107//! - `shallow` flag: Disabled by default. When both the `export` and `shallow`
108//!   features are enabled, the `#[export]` macro generates the necessary traits
109//!   with dummy implementations. This mode is used for API development purposes
110//!   when you don't need to run the script engine, as the Rust compiler
111//!   processes the source code much faster with dummy implementations.
112//! - `lsp` flag: Enabled by default. When this feature is disabled, the
113//!   `server` module of this crate is not available.
114//!
115//! ## Quick Links
116//!
117//! - [GitHub Repository](https://github.com/Eliah-Lakhin/ad-astra)
118//! - [API Documentation](https://docs.rs/ad-astra)
119//! - [Main Crate](https://crates.io/crates/ad-astra)
120//! - [Guide Book](https://ad-astra.lakhin.com)
121//! - [Examples](https://github.com/Eliah-Lakhin/ad-astra/tree/master/work/examples)
122//! - [Playground](https://ad-astra.lakhin.com/playground.html)
123//!
124//! ## Copyright
125//!
126//! This work is proprietary software with source-available code.
127//!
128//! To copy, use, distribute, or contribute to this work, you must agree to the
129//! terms and conditions of the
130//! [General License Agreement](https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md).
131//!
132//! For an explanation of the licensing terms, see the
133//! [F.A.Q.](https://github.com/Eliah-Lakhin/ad-astra/tree/master/FAQ.md)
134//!
135//! Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин). All rights reserved.
136
137/// Creation, editing, and incremental analysis of script modules.
138///
139/// This API allows you to load scripts from disk, print their source code
140/// diagnostics (errors and warnings) to the terminal, and compile these modules
141/// into Ad Astra VM assembly ready for execution.
142///
143/// Additionally, the analysis module provides a low-level API for developing
144/// your own source code analysis tools from scratch. The script module objects
145/// created with this API are editable, and they include methods for querying
146/// individual semantic features of the source code. For example, you can
147/// request the type of a variable in the source code or find all usages of
148/// identifiers across the code.
149///
150/// For further details, see the [ScriptModule](analysis::ScriptModule)
151/// documentation.
152///
153/// ## Script Modules Creation
154///
155/// ```rust
156/// # use ad_astra::{
157/// #     analysis::ScriptModule, export, lady_deirdre::analysis::TriggerHandle,
158/// #     runtime::ScriptPackage,
159/// # };
160/// #
161/// // To instantiate modules, you must declare a script package for your crate.
162/// //
163/// // The script analysis and execution occur within the package environment,
164/// // which keeps track of all Rust items you make available for the script
165/// // runtime.
166/// //
167/// // Usually, you declare the Package object in the lib.rs or main.rs file of
168/// // your crate.
169/// #[export(package)]
170/// #[derive(Default)]
171/// struct Package;
172///
173/// let _module = ScriptModule::<TriggerHandle>::new(
174///     // A reference to the package under which the source code will be analyzed.
175///     Package::meta(),
176///     // The source code text. This could be text that you load from disk.
177///     "let foo = 10;",
178/// );
179/// ```
180///
181/// ## Source Code Diagnostics
182///
183/// ```rust
184/// # use ad_astra::{
185/// #     analysis::{IssueSeverity, ModuleRead, ScriptModule},
186/// #     export,
187/// #     lady_deirdre::analysis::TriggerHandle,
188/// #     runtime::ScriptPackage,
189/// # };
190/// #
191/// # #[export(package)]
192/// # #[derive(Default)]
193/// # struct Package;
194/// #
195/// let module = ScriptModule::new(Package::meta(), "let foo = ;");
196///
197/// // First, you need to access the module for reading (you cannot read and
198/// // write to the module at the same time).
199/// //
200/// // A handle object is required. Using this object, you can signal the
201/// // read part to interrupt this job from another thread where you want to
202/// // write to the module.
203/// let handle = TriggerHandle::new();
204/// let module_read = module.read(&handle, 1).unwrap();
205///
206/// // Diagnostics Level 1 -- all syntax errors.
207/// // Diagnostics Level 2 -- superficial semantic errors and warnings.
208/// // Diagnostics Level 3 -- deep analysis for semantic warnings.
209/// let diagnostics_1 = module_read.diagnostics(1 /* all syntax errors */).unwrap();
210///
211/// // To print errors you need access to the module's source code text.
212/// let module_text = module_read.text();
213///
214/// println!(
215///     "{}",
216///     diagnostics_1.highlight(
217///         &module_text,
218///         IssueSeverity::Error as u8, /* print error messages only  */
219///     ),
220/// );
221/// ```
222///
223/// ## Changing Source Code Text
224///
225/// ```rust
226/// # use ad_astra::{
227/// #     analysis::{ModuleRead, ModuleWrite, ScriptModule},
228/// #     export,
229/// #     lady_deirdre::{
230/// #         analysis::TriggerHandle,
231/// #         lexis::{Position, SourceCode},
232/// #     },
233/// #     runtime::ScriptPackage,
234/// # };
235/// #
236/// # #[export(package)]
237/// # #[derive(Default)]
238/// # struct Package;
239/// #
240/// let module = ScriptModule::new(Package::meta(), "let foo = 10;");
241///
242/// // Access the module for writing.
243/// let handle = TriggerHandle::new();
244/// let mut module_write = module.write(&handle, 1).unwrap();
245///
246/// // Change the text on line 1, columns 11 to 13 (exclusive).
247/// module_write
248///     .edit(Position::new(1, 11)..Position::new(1, 13), "20")
249///     .unwrap();
250///
251/// let module_text = module_write.text();
252///
253/// assert_eq!(module_text.substring(..), "let foo = 20;");
254/// ```
255///
256/// ## Query Symbol Semantics
257///
258/// ```rust
259/// # use ad_astra::{
260/// #     analysis::{
261/// #         symbols::{LookupOptions, ModuleSymbol},
262/// #         ModuleRead,
263/// #         ScriptModule,
264/// #     },
265/// #     export,
266/// #     lady_deirdre::{analysis::TriggerHandle, lexis::Position},
267/// #     runtime::ScriptPackage,
268/// # };
269/// #
270/// # #[export(package)]
271/// # #[derive(Default)]
272/// # struct Package;
273/// #
274/// let module = ScriptModule::new(Package::meta(), "let foo = 10; let bar = foo;");
275///
276/// let handle = TriggerHandle::new();
277/// let module_read = module.read(&handle, 1).unwrap();
278///
279/// // Reads symbol(s) in the specified source code span.
280/// let symbols = module_read
281///     .symbols(
282///         Position::new(1, 19)..Position::new(1, 22), // "bar"
283///         LookupOptions::default(), // You can filter specific symbol types.
284///     )
285///     .unwrap();
286///
287/// let ModuleSymbol::Var(var_symbol) = symbols.first().unwrap() else {
288///     panic!();
289/// };
290///
291/// let var_type = var_symbol.var_type(&module_read).unwrap().type_hint;
292///
293/// assert_eq!("number", var_type.to_string());
294/// ```
295pub mod analysis;
296
297mod exports;
298
299/// Scripts formatting and printing to the terminal.
300///
301/// This module contains two useful components:
302///
303/// 1. A source code formatting algorithm (available through the
304///    [format_script_text](format::format_script_text) function). This
305///    function applies canonical formatting rules to source code text
306///    written in the Ad Astra language.
307///
308/// 2. The [ScriptSnippet](format::ScriptSnippet) object, which allows you
309///    to print snippets with syntax highlighting and annotated fragments of
310///    Ad Astra source code to the terminal.
311pub mod format;
312
313/// Ad Astra Virtual Machine.
314///
315/// The primary object of this module is the [ScriptFn](interpret::ScriptFn),
316/// which contains the compiled assembly of the source code ready for execution.
317/// You create this object using the [compile](analysis::ModuleRead::compile)
318/// function.
319///
320/// You can find more information about the internal design of the Virtual
321/// Machine in the ScriptFn API documentation.
322pub mod interpret;
323
324mod report;
325
326/// Building blocks of the script evaluation runtime.
327///
328/// This module provides both high-level and low-level APIs related to the
329/// script execution engine, memory management, and the export system:
330///
331/// - The [ScriptPackage](runtime::ScriptPackage) and
332///   [PackageMeta](runtime::PackageMeta) interfaces describe the package of
333///   your crate into which the export system exports introspection metadata
334///   of the Rust code related to your crate.
335///
336/// - The [Cell](runtime::Cell) object provides a generic interface to interact
337///   with the memory managed by the Script Engine.
338///
339/// - The [TypeMeta](runtime::TypeMeta), [Prototype](runtime::Prototype), and
340///   other related interfaces expose introspection information about the Rust
341///   types known to the script engine.
342///
343/// - The [Origin](runtime::Origin), [Ident](runtime::Ident), and other related
344///   objects provide ways to address both Rust and Script source code text
345///   ranges.
346///
347/// - The [runtime::ops] submodule provides interfaces for low-level modeling of
348///   Rust code exporting.
349pub mod runtime;
350
351mod semantics;
352
353/// Built-in language server for code editors that support the LSP protocol.
354///
355/// This module is available under the `lsp` feature of the crate, which is
356/// enabled by default.
357///
358/// ## LSP Protocol Overview
359///
360/// The language server is a standalone program that is typically bundled with
361/// a language extension for the code editor. Usually, the extension (acting as
362/// a "client") runs this program and then connects to it, for example, through
363/// an IO channel or a TCP socket.
364///
365/// Once the connection is established, the client and server communicate via
366/// the [LSP](https://microsoft.github.io/language-server-protocol/) protocol,
367/// which consists of identifiable two-way messages and one-way notifications.
368///
369/// The communication session is an ongoing process. During the initial setup
370/// phase, the client and server negotiate which code editor features they
371/// support. The session ends when the user closes the editor; in this case,
372/// the client either sends a special exit signal to the server or simply
373/// terminates the language server process.
374///
375/// During normal operation (after initialization and before session closing),
376/// the client notifies the server about changes in the source code of the
377/// project files being developed by the user. This includes events like files
378/// being opened or closed, and changes to the text of opened files.
379/// The server, in turn, creates and maintains an internal representation of the
380/// files open in the editor, including their text, syntax, and semantics.
381///
382/// The client periodically queries the server for information about the opened
383/// files. For example, the client might request type hints for the code
384/// currently visible in the editor's window. If the user starts typing, the
385/// client may ask the server for code completion suggestions. If the user
386/// hovers over a variable, the client queries the server for all semantically
387/// related usages of that variable, and so on.
388///
389/// Additionally, the server performs background tasks such as analyzing the
390/// source code for syntax and semantic errors and warnings, and periodically
391/// sends diagnostic information to the client.
392///
393/// Overall, the purpose of the language server is to provide the editor with
394/// useful information about the evolving codebase, which the editor uses to
395/// assist the user in developing the source code.
396///
397/// ## Setup
398///
399/// Typically, you run the language server from the main function using
400/// the [LspServer::startup](server::LspServer::startup) function, providing
401/// the server configuration objects and a reference to the
402/// [Package](runtime::PackageMeta) under which the server should analyze
403/// the source code.
404///
405/// ```no_run
406/// # use std::{net::SocketAddr, str::FromStr};
407/// #
408/// # use ad_astra::{
409/// #     export,
410/// #     runtime::ScriptPackage,
411/// #     server::{LspLoggerConfig, LspServer, LspServerConfig, LspTransportConfig},
412/// # };
413/// #
414/// #[export(package)]
415/// #[derive(Default)]
416/// struct Package;
417///
418/// let server_config = LspServerConfig::new();
419///
420/// let logger_config = LspLoggerConfig::new();
421///
422/// let transport_config =
423///     LspTransportConfig::TcpServer(SocketAddr::from_str("127.0.0.1:8081").unwrap());
424///
425/// LspServer::startup(
426///     // General server and LSP features configuration.
427///     server_config,
428///     // Server-side and client-side logger configuration.
429///     logger_config,
430///     // Transport configuration between the server and the editor (client).
431///     transport_config,
432///     // Script package under which the opened files will be analyzed.
433///     Package::meta(),
434/// );
435/// ```
436///
437/// This startup function creates the server and establishes a communication
438/// session with the client. When the communication session ends, the function
439/// returns, and typically the server process terminates at this stage as well.
440///
441/// For the communication channel, most editors in production prefer the STD-IO
442/// channel rather than the TCP socket. However, in debug mode, a TCP socket
443/// can be more convenient as it provides an easier way to restart the server
444/// and the client.
445///
446/// ## WebAssembly
447///
448/// The Ad Astra crate (including the LSP server) supports WebAssembly (wasm)
449/// build targets, allowing you to potentially run the LSP server directly in a
450/// web browser.
451///
452/// However, this setup requires additional effort to establish the
453/// communication channel between the wasm module and the client.
454///
455/// First, you need to disable the multi-threaded mode of the LSP server
456/// by setting the `multi_thread` option of the
457/// [LspServerConfig](server::LspServerConfig) to false, as wasm builds
458/// typically do not support multi-threading.
459///
460/// Next, create the server object manually using the
461/// [LspServer::new](server::LspServer::new) constructor. By manually
462/// instantiating the server, communication is not automatically established.
463/// Instead, the server provides manual functions to handle incoming and
464/// outgoing messages.
465///
466/// The [LspServer::handle](server::LspServer::handle) function accepts
467/// incoming [RpcMessage](server::RpcMessage) objects from the client. You can
468/// deserialize these objects from a byte array or a UTF-8 encoded string
469/// using [RpcMessage::from_input_bytes](server::RpcMessage::from_input_bytes).
470///
471/// To send outgoing messages from the server to the client, you should create
472/// a Rust standard channel for outgoing RPC messages using
473/// [RpcMessage::channel](server::RpcMessage::channel). One end of the
474/// channel is passed to the server's constructor, while the other end
475/// is used to read pending messages that the server wants to send to the client.
476///
477/// ```no_run
478/// use ad_astra::{
479///     export,
480///     runtime::ScriptPackage,
481///     server::{LspServer, LspServerConfig, RpcMessage},
482/// };
483///
484/// #[export(package)]
485/// #[derive(Default)]
486/// struct Package;
487///
488/// let mut server_config = LspServerConfig::new();
489///
490/// server_config.multi_thread = false;
491///
492/// let (outgoing_sender, outgoing_receiver) = RpcMessage::channel();
493///
494/// let mut server = LspServer::new(server_config, Package::meta(), outgoing_sender);
495///
496/// loop {
497///     let in_message = RpcMessage::from_input_bytes(
498///         // Should be a message received from the wasm host (from the client).
499///         &[],
500///     )
501///     .unwrap();
502///
503///     if in_message.is_exit() {
504///         return;
505///     }
506///
507///     server.handle(in_message);
508///
509///     while let Ok(out_message) = outgoing_receiver.try_recv() {
510///         let out_bytes = out_message.to_output_bytes().unwrap();
511///
512///         // Send the out_bytes back to the wasm host (the client).
513///     }
514/// }
515/// ```
516#[cfg(feature = "lsp")]
517pub mod server;
518mod syntax;
519
520extern crate self as ad_astra;
521
522pub use ad_astra_export::export;
523pub use lady_deirdre;