Skip to content

Commit b5f8f51

Browse files
committed
Add Lua bindings for building stack graphs
We're using the `mlua` crate, since it seems most actively maintained and performant.
1 parent 5d49f9b commit b5f8f51

17 files changed

+1038
-7
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
- name: Run lsp-positions tests without tree-sitter
5656
run: cargo test -p lsp-positions --no-default-features
5757
- name: Run test suite with all features enabled
58-
run: cargo test --all-features
58+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
5959
- name: Run test suite with all optimizations
6060
run: cargo test --release
6161
# Do the new project test last, because it adds the crate in the current source

.github/workflows/publish-lsp-positions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

.github/workflows/publish-stack-graphs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

.github/workflows/publish-tree-sitter-stack-graphs-java.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

.github/workflows/publish-tree-sitter-stack-graphs-javascript.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

.github/workflows/publish-tree-sitter-stack-graphs-typescript.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

.github/workflows/publish-tree-sitter-stack-graphs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v3
2020
# TODO Verify the crate version matches the tag
2121
- name: Test crate
22-
run: cargo test --all-features
22+
run: cargo test --all-features --features=mlua/lua54,mlua/vendored
2323
working-directory: ${{ env.CRATE_DIR }}
2424
- name: Verify publish crate
2525
run: cargo publish --dry-run

lsp-positions/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ test = false
1818

1919
[features]
2020
bincode = ["dep:bincode"]
21+
lua = ["mlua"]
2122
tree-sitter = ["dep:tree-sitter"]
2223

2324
[dependencies]
2425
memchr = "2.4"
26+
mlua = { version = "0.9", optional = true }
2527
tree-sitter = { version=">= 0.19", optional=true }
2628
unicode-segmentation = { version="1.8" }
2729
serde = { version="1", optional=true, features=["derive"] }

lsp-positions/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ use memchr::memchr;
3333

3434
use unicode_segmentation::UnicodeSegmentation as _;
3535

36+
#[cfg(feature = "lua")]
37+
mod lua;
38+
3639
fn grapheme_len(string: &str) -> usize {
3740
string.graphemes(true).count()
3841
}

lsp-positions/src/lua.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// -*- coding: utf-8 -*-
2+
// ------------------------------------------------------------------------------------------------
3+
// Copyright © 2023, stack-graphs authors.
4+
// Licensed under either of Apache License, Version 2.0, or MIT license, at your option.
5+
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
6+
// ------------------------------------------------------------------------------------------------
7+
8+
use std::ops::Range;
9+
10+
use mlua::Error;
11+
use mlua::FromLua;
12+
use mlua::IntoLua;
13+
use mlua::Lua;
14+
use mlua::Value;
15+
16+
use crate::Offset;
17+
use crate::Position;
18+
use crate::Span;
19+
20+
impl<'lua> FromLua<'lua> for Offset {
21+
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
22+
let table = match value {
23+
Value::Table(table) => table,
24+
Value::Nil => return Ok(Offset::default()),
25+
_ => {
26+
return Err(mlua::Error::FromLuaConversionError {
27+
from: value.type_name(),
28+
to: "Offset",
29+
message: None,
30+
})
31+
}
32+
};
33+
let utf8_offset = table.get::<_, Option<_>>("utf8_offset")?.unwrap_or(0);
34+
let utf16_offset = table.get::<_, Option<_>>("utf16_offset")?.unwrap_or(0);
35+
let grapheme_offset = table.get::<_, Option<_>>("grapheme_offset")?.unwrap_or(0);
36+
Ok(Offset {
37+
utf8_offset,
38+
utf16_offset,
39+
grapheme_offset,
40+
})
41+
}
42+
}
43+
44+
impl<'lua> IntoLua<'lua> for Offset {
45+
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
46+
let result = l.create_table()?;
47+
result.set("utf8_offset", self.utf8_offset)?;
48+
result.set("utf16_offset", self.utf16_offset)?;
49+
result.set("grapheme_offset", self.grapheme_offset)?;
50+
Ok(Value::Table(result))
51+
}
52+
}
53+
54+
fn range_from_lua<'lua>(value: Value<'lua>) -> Result<Range<usize>, Error> {
55+
let table = match value {
56+
Value::Table(table) => table,
57+
Value::Nil => return Ok(0..0),
58+
_ => {
59+
return Err(mlua::Error::FromLuaConversionError {
60+
from: value.type_name(),
61+
to: "Range",
62+
message: None,
63+
})
64+
}
65+
};
66+
let start = table.get("start")?;
67+
let end = table.get("end")?;
68+
Ok(start..end)
69+
}
70+
71+
fn range_into_lua<'lua>(range: Range<usize>, l: &'lua Lua) -> Result<Value<'lua>, Error> {
72+
let result = l.create_table()?;
73+
result.set("start", range.start)?;
74+
result.set("end", range.end)?;
75+
Ok(Value::Table(result))
76+
}
77+
78+
impl<'lua> FromLua<'lua> for Position {
79+
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
80+
let table = match value {
81+
Value::Table(table) => table,
82+
Value::Nil => return Ok(Position::default()),
83+
_ => {
84+
return Err(mlua::Error::FromLuaConversionError {
85+
from: value.type_name(),
86+
to: "Position",
87+
message: None,
88+
})
89+
}
90+
};
91+
let line = table.get("line")?;
92+
let column = table.get("column")?;
93+
let containing_line = range_from_lua(table.get("containing_line")?)?;
94+
let trimmed_line = range_from_lua(table.get("trimmed_line")?)?;
95+
Ok(Position {
96+
line,
97+
column,
98+
containing_line,
99+
trimmed_line,
100+
})
101+
}
102+
}
103+
104+
impl<'lua> IntoLua<'lua> for Position {
105+
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
106+
let result = l.create_table()?;
107+
result.set("line", self.line)?;
108+
result.set("column", self.column)?;
109+
result.set("containing_line", range_into_lua(self.containing_line, l)?)?;
110+
result.set("trimmed_line", range_into_lua(self.trimmed_line, l)?)?;
111+
Ok(Value::Table(result))
112+
}
113+
}
114+
115+
impl<'lua> FromLua<'lua> for Span {
116+
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self, Error> {
117+
let table = match value {
118+
Value::Table(table) => table,
119+
Value::Nil => return Ok(Span::default()),
120+
_ => {
121+
return Err(mlua::Error::FromLuaConversionError {
122+
from: value.type_name(),
123+
to: "Span",
124+
message: None,
125+
})
126+
}
127+
};
128+
let start = table.get("start")?;
129+
let end = table.get("end")?;
130+
Ok(Span { start, end })
131+
}
132+
}
133+
134+
impl<'lua> IntoLua<'lua> for Span {
135+
fn into_lua(self, l: &'lua Lua) -> Result<Value<'lua>, Error> {
136+
if self == Span::default() {
137+
return Ok(Value::Nil);
138+
}
139+
let result = l.create_table()?;
140+
result.set("start", self.start)?;
141+
result.set("end", self.end)?;
142+
Ok(Value::Table(result))
143+
}
144+
}

stack-graphs/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## v0.13.0 -- Unreleased
9+
10+
### Added
11+
12+
- Added Lua bindings for constructing stack graphs. These bindings are optional, and will only be built when the `lua` feature flag is enabled.
13+
814
## v0.12.0 -- 2023-07-27
915

1016
### Added

stack-graphs/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ edition = "2018"
1515
[features]
1616
bincode = ["dep:bincode", "lsp-positions/bincode"]
1717
copious-debugging = []
18+
lua = ["mlua", "lsp-positions/lua"]
1819
serde = ["dep:serde", "serde_with", "lsp-positions/serde"]
1920
storage = ["bincode", "rusqlite"]
2021
visualization = ["serde", "serde_json"]
@@ -32,6 +33,7 @@ enumset = "1.1"
3233
fxhash = "0.2"
3334
itertools = "0.10"
3435
libc = "0.2"
36+
mlua = { version = "0.9", optional = true }
3537
lsp-positions = { version = "0.3", path = "../lsp-positions" }
3638
rusqlite = { version = "0.28", optional = true, features = ["bundled", "functions"] }
3739
serde = { version = "1.0", optional = true, features = ["derive"] }
@@ -41,6 +43,7 @@ smallvec = { version = "1.6", features = ["union"] }
4143
thiserror = { version = "1.0" }
4244

4345
[dev-dependencies]
46+
anyhow = "1.0"
4447
assert-json-diff = "2"
4548
itertools = "0.10"
4649
maplit = "1.0"
@@ -49,3 +52,5 @@ serde_json = { version = "1.0" }
4952

5053
[package.metadata.docs.rs]
5154
all-features = true
55+
features = ["mlua/lua54", "mlua/vendored"]
56+
rustdoc-args = ["--cfg", "docsrs"]

stack-graphs/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@ how to use this library.
1717

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

20+
## Lua bindings
21+
22+
This crate includes optional Lua bindings, allowing you to construct stack
23+
graphs using Lua code. Lua support is only enabled if you compile with the `lua`
24+
feature. This feature is not enough on its own, because the `mlua` crate
25+
supports multiple Lua versions, and can either link against a system-installed
26+
copy of Lua, or build its own copy from vendored Lua source. These choices are
27+
all controlled via additional features on the `mlua` crate.
28+
29+
When building and testing this crate, make sure to provide all necessary
30+
features on the command line:
31+
32+
``` console
33+
$ cargo test --features lua,mlua/lua54,mlua/vendored
34+
```
35+
36+
When building a crate that depends on this crate, add a dependency on `mlua` so
37+
that you can set its feature flags:
38+
39+
``` toml
40+
[dependencies]
41+
stack-graphs = { version="0.13", features=["lua"] }
42+
mlua = { version="0.9", features=["lua54", "vendored"] }
43+
```
44+
2045
## Credits
2146

2247
Stack graphs are heavily based on the [_scope graphs_][scope graphs] framework

stack-graphs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ pub mod cycles;
6666
#[macro_use]
6767
mod debugging;
6868
pub mod graph;
69+
#[cfg(feature = "lua")]
70+
pub mod lua;
6971
pub mod partial;
7072
pub mod paths;
7173
pub mod serde;

0 commit comments

Comments
 (0)