Skip to content
This repository was archived by the owner on Sep 9, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add Lua bindings for building stack graphs
We're using the `mlua` crate, since it seems most actively maintained
and performant.
  • Loading branch information
dcreager committed Nov 20, 2023
commit f96b8d65d895bf4567dec64b01a8229789d873e7
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
- name: Run lsp-positions tests without tree-sitter
run: cargo test -p lsp-positions --no-default-features
- name: Run test suite with all features enabled
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
- name: Run test suite with all optimizations
run: cargo test --release
# Do the new project test last, because it adds the crate in the current source
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-lsp-positions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-stack-graphs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-tree-sitter-stack-graphs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v3
# TODO Verify the crate version matches the tag
- name: Test crate
run: cargo test --all-features
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
working-directory: ${{ env.CRATE_DIR }}
- name: Verify publish crate
run: cargo publish --dry-run
Expand Down
2 changes: 2 additions & 0 deletions lsp-positions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ test = false

[features]
bincode = ["dep:bincode"]
lua = ["dep:mlua"]
tree-sitter = ["dep:tree-sitter"]

[dependencies]
memchr = "2.4"
mlua = { version = "0.9", optional = true }
tree-sitter = { version=">= 0.19", optional=true }
unicode-segmentation = { version="1.8" }
serde = { version="1", optional=true, features=["derive"] }
Expand Down
3 changes: 3 additions & 0 deletions lsp-positions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ use memchr::memchr;

use unicode_segmentation::UnicodeSegmentation as _;

#[cfg(feature = "lua")]
mod lua;

fn grapheme_len(string: &str) -> usize {
string.graphemes(true).count()
}
Expand Down
144 changes: 144 additions & 0 deletions lsp-positions/src/lua.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// -*- coding: utf-8 -*-
// ------------------------------------------------------------------------------------------------
// Copyright © 2023, stack-graphs authors.
// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
// ------------------------------------------------------------------------------------------------

use std::ops::Range;

use mlua::Error;
use mlua::FromLua;
use mlua::IntoLua;
use mlua::Lua;
use mlua::Value;

use crate::Offset;
use crate::Position;
use crate::Span;

impl<'lua> FromLua<'lua> for Offset {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
let table = match value {
Value::Table(table) => table,
Value::Nil => return Ok(Offset::default()),
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: "Offset",
message: None,
})
}
};
let utf8_offset = table.get::<_, Option<_>>("utf8_offset")?.unwrap_or(0);
let utf16_offset = table.get::<_, Option<_>>("utf16_offset")?.unwrap_or(0);
let grapheme_offset = table.get::<_, Option<_>>("grapheme_offset")?.unwrap_or(0);
Ok(Offset {
utf8_offset,
utf16_offset,
grapheme_offset,
})
}
}

impl<'lua> IntoLua<'lua> for Offset {
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
let result = l.create_table()?;
result.set("utf8_offset", self.utf8_offset)?;
result.set("utf16_offset", self.utf16_offset)?;
result.set("grapheme_offset", self.grapheme_offset)?;
Ok(Value::Table(result))
}
}

fn range_from_lua<'lua>(value: Value<'lua>) -> Result<Range<usize>, Error> {
let table = match value {
Value::Table(table) => table,
Value::Nil => return Ok(0..0),
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: "Range",
message: None,
})
}
};
let start = table.get("start")?;
let end = table.get("end")?;
Ok(start..end)
}

fn range_into_lua<'lua>(range: Range<usize>, l: &'lua Lua) -> Result<Value<'lua>, Error> {
let result = l.create_table()?;
result.set("start", range.start)?;
result.set("end", range.end)?;
Ok(Value::Table(result))
}

impl<'lua> FromLua<'lua> for Position {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
let table = match value {
Value::Table(table) => table,
Value::Nil => return Ok(Position::default()),
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: "Position",
message: None,
})
}
};
let line = table.get("line")?;
let column = table.get("column")?;
let containing_line = range_from_lua(table.get("containing_line")?)?;
let trimmed_line = range_from_lua(table.get("trimmed_line")?)?;
Ok(Position {
line,
column,
containing_line,
trimmed_line,
})
}
}

impl<'lua> IntoLua<'lua> for Position {
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
let result = l.create_table()?;
result.set("line", self.line)?;
result.set("column", self.column)?;
result.set("containing_line", range_into_lua(self.containing_line, l)?)?;
result.set("trimmed_line", range_into_lua(self.trimmed_line, l)?)?;
Ok(Value::Table(result))
}
}

impl<'lua> FromLua<'lua> for Span {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
let table = match value {
Value::Table(table) => table,
Value::Nil => return Ok(Span::default()),
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: "Span",
message: None,
})
}
};
let start = table.get("start")?;
let end = table.get("end")?;
Ok(Span { start, end })
}
}

impl<'lua> IntoLua<'lua> for Span {
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
if self == Span::default() {
return Ok(Value::Nil);
}
let result = l.create_table()?;
result.set("start", self.start)?;
result.set("end", self.end)?;
Ok(Value::Table(result))
}
}
6 changes: 6 additions & 0 deletions stack-graphs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.13.0 -- Unreleased

### Added

- Added Lua bindings for constructing stack graphs. These bindings are optional, and will only be built when the `lua` feature flag is enabled.

## v0.12.0 -- 2023-07-27

### Added
Expand Down
5 changes: 5 additions & 0 deletions stack-graphs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ edition = "2018"
[features]
bincode = ["dep:bincode", "lsp-positions/bincode"]
copious-debugging = []
lua = ["dep:mlua", "lsp-positions/lua"]
serde = ["dep:serde", "serde_with", "lsp-positions/serde"]
storage = ["bincode", "rusqlite"]
visualization = ["serde", "serde_json"]
Expand All @@ -33,6 +34,7 @@ fxhash = "0.2"
itertools = "0.10"
libc = "0.2"
lsp-positions = { version = "0.3", path = "../lsp-positions" }
mlua = { version = "0.9", optional = true }
rusqlite = { version = "0.28", optional = true, features = ["bundled", "functions"] }
serde = { version = "1.0", optional = true, features = ["derive"] }
serde_json = { version = "1.0", optional = true }
Expand All @@ -41,6 +43,7 @@ smallvec = { version = "1.6", features = ["union"] }
thiserror = { version = "1.0" }

[dev-dependencies]
anyhow = "1.0"
assert-json-diff = "2"
itertools = "0.10"
maplit = "1.0"
Expand All @@ -49,3 +52,5 @@ serde_json = { version = "1.0" }

[package.metadata.docs.rs]
all-features = true
features = ["mlua/lua54", "mlua/vendored"]
rustdoc-args = ["--cfg", "docsrs"]
25 changes: 25 additions & 0 deletions stack-graphs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ how to use this library.

Notable changes for each version are documented in the [release notes](https://github.com/github/stack-graphs/blob/main/stack-graphs/CHANGELOG.md).

## Lua bindings

This crate includes optional Lua bindings, allowing you to construct stack
graphs using Lua code. Lua support is only enabled if you compile with the `lua`
feature. This feature is not enough on its own, because the `mlua` crate
supports multiple Lua versions, and can either link against a system-installed
copy of Lua, or build its own copy from vendored Lua source. These choices are
all controlled via additional features on the `mlua` crate.

When building and testing this crate, make sure to provide all necessary
features on the command line:

``` console
$ cargo test --features lua,mlua/lua54,mlua/vendored
```

When building a crate that depends on this crate, add a dependency on `mlua` so
that you can set its feature flags:

``` toml
[dependencies]
stack-graphs = { version="0.13", features=["lua"] }
mlua = { version="0.9", features=["lua54", "vendored"] }
```

## Credits

Stack graphs are heavily based on the [_scope graphs_][scope graphs] framework
Expand Down
2 changes: 2 additions & 0 deletions stack-graphs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub mod cycles;
#[macro_use]
mod debugging;
pub mod graph;
#[cfg(feature = "lua")]
pub mod lua;
pub mod partial;
pub mod paths;
pub mod serde;
Expand Down
Loading