Skip to content

Commit 6f6077c

Browse files
committed
feat: add load package with cache API
Signed-off-by: peefy <[email protected]>
1 parent 25f22db commit 6f6077c

File tree

5 files changed

+134
-1
lines changed

5 files changed

+134
-1
lines changed

java/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@ doc = false
99

1010
[dependencies]
1111
jni = "0.21.1"
12+
prost = "0.11.8"
13+
prost-types = "0.11.8"
14+
serde_json = "1"
15+
indexmap = "2.2.5"
1216
anyhow = "1"
17+
serde = { version = "1", features = ["derive"] }
1318
kcl-lang = {path = "../"}
1419
once_cell = "1.19.0"
1520
lazy_static = "1.4.0"
21+
kclvm-parser = { git = "https://github.com/peefy/KCLVM", version = "0.8.1" }
22+
kclvm-sema = { git = "https://github.com/peefy/KCLVM", version = "0.8.1" }
23+
kclvm-api = { git = "https://github.com/peefy/KCLVM", version = "0.8.1" }

java/src/lib.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
extern crate anyhow;
22
extern crate jni;
33
extern crate kcl_lang;
4+
extern crate kclvm_api;
5+
extern crate kclvm_parser;
6+
extern crate kclvm_sema;
47
extern crate lazy_static;
58
extern crate once_cell;
9+
extern crate prost;
610

711
use anyhow::Result;
812
use jni::objects::{JByteArray, JClass, JObject};
913
use jni::sys::jbyteArray;
1014
use jni::JNIEnv;
15+
use kcl_lang::API;
16+
use kclvm_api::gpyrpc::LoadPackageArgs;
17+
use kclvm_api::service::KclvmServiceImpl;
18+
use kclvm_parser::KCLModuleCache;
19+
use kclvm_sema::resolver::scope::KCLScopeCache;
1120
use lazy_static::lazy_static;
1221
use once_cell::sync::OnceCell;
22+
use prost::Message;
1323
use std::sync::Mutex;
1424

1525
lazy_static! {
16-
static ref API_INSTANCE: Mutex<OnceCell<kcl_lang::API>> = Mutex::new(OnceCell::new());
26+
static ref API_INSTANCE: Mutex<OnceCell<API>> = Mutex::new(OnceCell::new());
27+
static ref MODULE_CACHE: Mutex<OnceCell<KCLModuleCache>> = Mutex::new(OnceCell::new());
28+
static ref SCOPE_CACHE: Mutex<OnceCell<KCLScopeCache>> = Mutex::new(OnceCell::new());
1729
}
1830

1931
#[no_mangle]
@@ -29,6 +41,18 @@ pub extern "system" fn Java_com_kcl_api_API_callNative(
2941
})
3042
}
3143

44+
#[no_mangle]
45+
pub extern "system" fn Java_com_kcl_api_API_loadPackageWithCache(
46+
mut env: JNIEnv,
47+
_: JClass,
48+
args: JByteArray,
49+
) -> jbyteArray {
50+
intern_load_package_with_cache(&mut env, args).unwrap_or_else(|e| {
51+
let _ = throw(&mut env, e);
52+
JObject::default().into_raw()
53+
})
54+
}
55+
3256
fn intern_call_native(env: &mut JNIEnv, name: JByteArray, args: JByteArray) -> Result<jbyteArray> {
3357
let binding = API_INSTANCE.lock().unwrap();
3458
let api = binding.get_or_init(|| kcl_lang::API::new().expect("Failed to create API instance"));
@@ -39,6 +63,25 @@ fn intern_call_native(env: &mut JNIEnv, name: JByteArray, args: JByteArray) -> R
3963
Ok(j_byte_array.into_raw())
4064
}
4165

66+
/// This is a stateful API, so we avoid serialization overhead and directly use JVM
67+
/// to instantiate global variables here.
68+
fn intern_load_package_with_cache(env: &mut JNIEnv, args: JByteArray) -> Result<jbyteArray> {
69+
// AST module cache
70+
let binding = MODULE_CACHE.lock().unwrap();
71+
let module_cache = binding.get_or_init(|| KCLModuleCache::default());
72+
// Resolver scope cache
73+
let binding = SCOPE_CACHE.lock().unwrap();
74+
let scope_cache = binding.get_or_init(|| KCLScopeCache::default());
75+
// Load package arguments from protobuf bytes.
76+
let args = env.convert_byte_array(args)?;
77+
let args: LoadPackageArgs = <LoadPackageArgs as Message>::decode(args.as_ref())?;
78+
let svc = KclvmServiceImpl::default();
79+
// Call load package API and decode the result to protobuf bytes.
80+
let packages = svc.load_package_with_cache(&args, module_cache.clone(), scope_cache.clone())?;
81+
let j_byte_array = env.byte_array_from_slice(&packages.encode_to_vec())?;
82+
Ok(j_byte_array.into_raw())
83+
}
84+
4285
fn throw(env: &mut JNIEnv, error: anyhow::Error) -> jni::errors::Result<()> {
4386
env.throw(("java/lang/Exception", error.to_string()))
4487
}

java/src/main/java/com/kcl/api/API.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ private static String bundledLibraryPath() {
5656
}
5757

5858
private native byte[] callNative(byte[] call, byte[] args);
59+
private native byte[] loadPackageWithCache(byte[] args);
5960

6061
public API() {
6162
}
@@ -132,6 +133,39 @@ public LoadPackage_Result loadPackage(LoadPackage_Args args) throws Exception {
132133
return LoadPackage_Result.parseFrom(call("KclvmService.LoadPackage", args.toByteArray()));
133134
}
134135

136+
/**
137+
* Loads KCL package with the internal cache and returns the AST, symbol, type, definition information. *
138+
*
139+
* <p>
140+
* Example usage:
141+
*
142+
* <pre>
143+
* {@code
144+
* import com.kcl.api.*;
145+
*
146+
* API api = new API();
147+
* LoadPackage_Result result = api.loadPackageWithCache(
148+
* LoadPackage_Args.newBuilder().setResolveAst(true).setParseArgs(
149+
* ParseProgram_Args.newBuilder().addPaths("/path/to/kcl.k").build())
150+
* .build());
151+
* result.getSymbolsMap().values().forEach(s -> System.out.println(s));
152+
* }
153+
* </pre>
154+
*
155+
* @param args
156+
* the arguments specifying the file paths to be parsed and resolved.
157+
*
158+
* @return the result of parsing the program and parse errors, type errors, including the AST in JSON format and
159+
* symbol, type and definition information.
160+
*
161+
* @throws Exception
162+
* if an error occurs during the remote procedure call.
163+
*/
164+
@Override
165+
public LoadPackage_Result loadPackageWithCache(LoadPackage_Args args) throws Exception {
166+
return LoadPackage_Result.parseFrom(callLoadPackageWithCache(args.toByteArray()));
167+
}
168+
135169
public ExecProgram_Result execProgram(ExecProgram_Args args) throws Exception {
136170
return ExecProgram_Result.parseFrom(call("KclvmService.ExecProgram", args.toByteArray()));
137171
}
@@ -194,6 +228,14 @@ private byte[] call(String name, byte[] args) throws Exception {
194228
return result;
195229
}
196230

231+
private byte[] callLoadPackageWithCache(byte[] args) throws Exception {
232+
byte[] result = loadPackageWithCache(args);
233+
if (result != null && startsWith(result, "ERROR")) {
234+
throw new java.lang.Error(result.toString());
235+
}
236+
return result;
237+
}
238+
197239
static boolean startsWith(byte[] array, String prefix) {
198240
byte[] prefixBytes = prefix.getBytes();
199241
if (array.length < prefixBytes.length) {

java/src/main/java/com/kcl/api/Service.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public interface Service {
1212
// Loads KCL package and returns the AST, symbol, type, definition information.
1313
LoadPackage_Result loadPackage(LoadPackage_Args args) throws Exception;
1414

15+
// Loads KCL package and returns the AST, symbol, type, definition information.
16+
LoadPackage_Result loadPackageWithCache(LoadPackage_Args args) throws Exception;
17+
1518
// Execute KCL file with args
1619
ExecProgram_Result execProgram(ExecProgram_Args args) throws Exception;
1720

java/src/test/java/com/kcl/LoadPackageTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,41 @@ public void testProgramSymbols() throws Exception {
5151
// Query Scope using the symbol
5252
Assert.assertEquals(SematicUtil.findScopeBySymbol(result, appSymbolIndex).getDefsCount(), 2);
5353
}
54+
55+
@Test
56+
public void testProgramSymbolsWithCache() throws Exception {
57+
// API instance
58+
API api = new API();
59+
// Note call `loadPackageWithCache` here.
60+
LoadPackage_Result result = api.loadPackageWithCache(LoadPackage_Args.newBuilder().setResolveAst(true)
61+
.setWithAstIndex(true)
62+
.setParseArgs(ParseProgram_Args.newBuilder().addPaths("./src/test_data/schema.k").build()).build());
63+
// Get parse errors
64+
Assert.assertEquals(result.getParseErrorsList().size(), 0);
65+
// Get Type errors
66+
Assert.assertEquals(result.getTypeErrorsList().size(), 0);
67+
// Get AST
68+
Program program = JsonUtil.deserializeProgram(result.getProgram());
69+
Assert.assertTrue(program.getRoot().contains("test_data"));
70+
// Variable definitions in the main scope.
71+
Scope mainScope = SematicUtil.findMainPackageScope(result);
72+
// Child scopes of the main scope.
73+
Assert.assertEquals(mainScope.getChildrenList().size(), 2);
74+
// Mapping AST node to Symbol type
75+
NodeRef<Stmt> stmt = program.getFirstModule().getBody().get(0);
76+
Assert.assertTrue(SematicUtil.findSymbolByAstId(result, stmt.getId()).getName().contains("pkg"));
77+
// Mapping symbol to AST node
78+
SymbolIndex appSymbolIndex = mainScope.getDefs(1);
79+
Symbol appSymbol = SematicUtil.findSymbol(result, appSymbolIndex);
80+
Assert.assertEquals(appSymbol.getTy().getSchemaName(), "AppConfig");
81+
// Query type symbol using variable type.
82+
String schemaFullName = appSymbol.getTy().getPkgPath() + "." + appSymbol.getTy().getSchemaName();
83+
Symbol appConfigSymbol = SematicUtil.findSymbol(result,
84+
result.getFullyQualifiedNameMapOrDefault(schemaFullName, null));
85+
Assert.assertEquals(appConfigSymbol.getTy().getSchemaName(), "AppConfig");
86+
// Query AST node using the symbol
87+
Assert.assertNotNull(SematicUtil.findNodeBySymbol(result, appSymbolIndex));
88+
// Query Scope using the symbol
89+
Assert.assertEquals(SematicUtil.findScopeBySymbol(result, appSymbolIndex).getDefsCount(), 2);
90+
}
5491
}

0 commit comments

Comments
 (0)