Initial commit

This commit is contained in:
Shadowfacts 2022-05-30 11:29:44 -04:00
commit f9d3a5e397
9 changed files with 321 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
/target
# Added by cargo
#
# already existing elements were commented out
#/target
/Cargo.lock

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "splash-rs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

93
build.rs Normal file
View File

@ -0,0 +1,93 @@
fn main() {
link_swift();
link_swift_package("highlight-swift", "./highlight-swift/");
let target = get_swift_target_info();
// on linux we need to tell it to link against the swift stdlib,
// because the --static-swift-stdlib on swift build doesn't work for static libraries
// on macos, it all magically works
if target.target.unversioned_triple.contains("linux") {
// include the swift runtime library path in the rpath
target.paths.runtime_library_paths.iter().for_each(|path| {
println!("cargo:rustc-link-arg-Wl,-rpath={}", path);
});
// need to link against libswiftCore and libFoundation
println!("cargo:rustc-link-lib=dylib=swiftCore");
println!("cargo:rustc-link-lib=dylib=Foundation");
}
}
// Copied from https://github.com/Brendonovich/swift-rs
// With get_swift_target_info changed to print the current target
// rather than always trying for the macosx target.
use std::{env, process::Command};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SwiftTargetInfo {
pub unversioned_triple: String,
#[serde(rename = "librariesRequireRPath")]
pub libraries_require_rpath: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SwiftPaths {
pub runtime_library_paths: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct SwiftTarget {
pub target: SwiftTargetInfo,
pub paths: SwiftPaths,
}
pub fn get_swift_target_info() -> SwiftTarget {
let swift_target_info_str = Command::new("swift")
.args(&["-print-target-info"])
.output()
.unwrap()
.stdout;
serde_json::from_slice(&swift_target_info_str).unwrap()
}
pub fn link_swift() {
let swift_target_info = get_swift_target_info();
if swift_target_info.target.libraries_require_rpath {
panic!("Libraries require RPath! Change minimum MacOS value to fix.")
}
swift_target_info
.paths
.runtime_library_paths
.iter()
.for_each(|path| {
println!("cargo:rustc-link-search=native={}", path);
});
}
pub fn link_swift_package(package_name: &str, package_root: &str) {
let profile = env::var("PROFILE").unwrap();
if !Command::new("swift")
.args(&["build", "-c", &profile])
.current_dir(package_root)
.status()
.unwrap()
.success()
{
panic!("Failed to compile swift package {}", package_name);
}
let swift_target_info = get_swift_target_info();
println!(
"cargo:rustc-link-search=native={}.build/{}/{}",
package_root, swift_target_info.target.unversioned_triple, profile
);
println!("cargo:rustc-link-lib=static={}", package_name);
}

9
highlight-swift/.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

View File

@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "splash",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JohnSundell/Splash.git",
"state" : {
"revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8",
"version" : "0.16.0"
}
}
],
"version" : 2
}

View File

@ -0,0 +1,29 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "highlight-swift",
platforms: [
.macOS(.v11),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "highlight-swift",
type: .static,
targets: ["highlight-swift"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/JohnSundell/Splash.git", .upToNextMinor(from: "0.16.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "highlight-swift",
dependencies: ["Splash"]),
]
)

View File

@ -0,0 +1,3 @@
# highlight-swift
A description of this package.

View File

@ -0,0 +1,120 @@
import Foundation
import Splash
@_cdecl("highlight_swift")
public func highlight(codePtr: UnsafeRawPointer, codeLen: UInt64, outPtr: UnsafeMutableRawPointer, maxLen: UInt64) -> UInt64 {
// don't free, the underlying data is owned by rust
let code = String(bytesNoCopy: UnsafeMutableRawPointer(mutating: codePtr), length: Int(codeLen), encoding: .utf8, freeWhenDone: false)!
let highligher = SyntaxHighlighter(format: MyOutputFormat())
var html = highligher.highlight(code)
precondition(html.utf8.count <= maxLen)
return html.withUTF8 { buf in
buf.copyBytes(to: UnsafeMutableRawBufferPointer(start: outPtr, count: Int(maxLen)))
return UInt64(buf.count)
}
}
struct MyOutputFormat: OutputFormat {
func makeBuilder() -> Builder {
return Builder()
}
struct Builder: OutputBuilder {
typealias Output = String
private var html = ""
private var pendingToken: (string: String, type: TokenType)?
private var pendingWhitespace: String?
mutating func addToken(_ token: String, ofType type: TokenType) {
if var pendingToken = pendingToken {
guard pendingToken.type != type else {
pendingWhitespace.map { pendingToken.string += $0 }
pendingWhitespace = nil
pendingToken.string += token
self.pendingToken = pendingToken
return
}
}
appendPending()
pendingToken = (token, type)
}
mutating func addPlainText(_ text: String) {
appendPending()
html.append(contentsOf: text.escapingHTMLEntities())
}
mutating func addWhitespace(_ whitespace: String) {
if pendingToken != nil {
pendingWhitespace = (pendingWhitespace ?? "") + whitespace
} else {
html.append(whitespace)
}
}
mutating func build() -> String {
appendPending()
return html
}
private mutating func appendPending() {
if let pendingToken = pendingToken {
let cls: String
switch pendingToken.type {
case .keyword:
cls = "hl-kw"
case .string:
cls = "hl-str"
case .type:
cls = "hl-type"
case .call:
cls = "hl-fn"
case .number:
cls = "hl-num"
case .comment:
cls = "hl-cmt"
case .property:
cls = "hl-var"
case .dotAccess:
cls = "hl-prop"
case .preprocessing:
cls = ""
case .custom(_):
cls = ""
}
html.append("""
<span class="\(cls)">\(pendingToken.string.escapingHTMLEntities())</span>
""")
self.pendingToken = nil
}
if let pendingWhitespace = pendingWhitespace {
html.append(pendingWhitespace)
self.pendingWhitespace = nil
}
}
}
}
// copied from Splash, where it's internal
extension StringProtocol {
func escapingHTMLEntities() -> String {
return String(flatMap { character -> String in
switch character {
case "&":
return "&amp;"
case "<":
return "&lt;"
case ">":
return "&gt;"
default:
return String(character)
}
})
}
}

34
src/lib.rs Normal file
View File

@ -0,0 +1,34 @@
pub fn highlight(code: &str) -> String {
unsafe {
let mut buf = Vec::new();
buf.reserve(
2usize
.pow(1 + (code.len() as f32).log2().ceil() as u32)
.max(1024),
);
let used = highlight_swift(
code.as_ptr(),
code.len() as u64,
buf.as_mut_ptr(),
buf.capacity() as u64,
);
buf.set_len(used as usize);
String::from_utf8_unchecked(buf)
}
}
extern "C" {
fn highlight_swift(code_ptr: *const u8, code_len: u64, out_ptr: *mut u8, max_len: u64) -> u64;
}
#[cfg(test)]
mod tests {
#[test]
fn test_highlight() {
let result = super::highlight("1+1");
assert_eq!(
result,
r#"<span class="hl-num">1</span>+<span class="hl-num">1</span>"#
);
}
}