Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
383 changes: 146 additions & 237 deletions AGENTS.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/plotnik-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ yaml = ["plotnik-langs/yaml"]

[dependencies]
clap = { version = "4.5", features = ["derive"] }
plotnik-core = { version = "0.1.0", path = "../plotnik-core" }
plotnik-langs = { version = "0.1.0", path = "../plotnik-langs", default-features = false }
plotnik-lib = { version = "0.1.0", path = "../plotnik-lib" }
serde_json = "1.0"
Expand Down
4 changes: 2 additions & 2 deletions crates/plotnik-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ pub enum Command {
plotnik exec -q '(identifier) @id' -s app.js --pretty
plotnik exec -q '(function_declaration) @fn' -s app.ts -l typescript --verbose-nodes
plotnik exec -q '(identifier) @id' -s app.js --check
plotnik exec --query-file query.plnk -s app.js --entry FunctionDef"#)]
plotnik exec --query-file query.ptk -s app.js --entry FunctionDef"#)]
Exec {
#[command(flatten)]
query: QueryArgs,
Expand All @@ -80,7 +80,7 @@ pub enum Command {
/// Generate type definitions from a query
#[command(after_help = r#"EXAMPLES:
plotnik types -q '(identifier) @id' -l javascript
plotnik types --query-file query.plnk -l typescript
plotnik types --query-file query.ptk -l typescript
plotnik types -q '(function_declaration) @fn' -l js --format ts
plotnik types -q '(identifier) @id' -l js --verbose-nodes
plotnik types -q '(identifier) @id' -l js -o types.d.ts
Expand Down
44 changes: 6 additions & 38 deletions crates/plotnik-cli/src/commands/debug/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![allow(dead_code)]
pub mod source;

use std::fs;
Expand Down Expand Up @@ -40,23 +41,21 @@ pub fn run(args: DebugArgs) {
};

let mut query = query_source.as_ref().map(|src| {
Query::try_from(src).unwrap_or_else(|e| {
Query::try_from(src.as_str()).unwrap_or_else(|e| {
eprintln!("error: {}", e);
std::process::exit(1);
})
});

// Auto-link when --lang is provided with a query
if args.lang.is_some()
&& let Some(ref mut q) = query
&& let Some(ref mut _q) = query
{
let lang = resolve_lang_for_link(&args.lang);
q.link(&lang);
unimplemented!();
}

let show_query = has_query_input && !args.symbols && !args.graph && !args.types;
let show_source = has_source_input;
let show_both_graphs = args.graph_raw && args.graph;

if show_query && let Some(ref q) = query {
print!(
Expand Down Expand Up @@ -84,40 +83,9 @@ pub fn run(args: DebugArgs) {

// Build graph if needed for --graph, --graph-raw, or --types
if (args.graph || args.graph_raw || args.types)
&& let Some(q) = query.take()
&& let Some(_) = query.take()
{
// Determine root kind for auto-wrapping
let root_kind = args.lang.as_ref().and_then(|lang_name| {
let lang = resolve_lang_for_link(&Some(lang_name.clone()));
lang.root().and_then(|root_id| lang.node_type_name(root_id))
});

let (q, pre_opt_dump) = q.build_graph_with_pre_opt_dump(root_kind);
let mut needs_separator = false;
if args.graph_raw {
if show_both_graphs {
println!("(pre-optimization)");
}
print!("{}", pre_opt_dump);
needs_separator = true;
}
if args.graph {
if needs_separator {
println!();
}
if show_both_graphs {
println!("(post-optimization)");
}
print!("{}", q.graph().dump_live(q.dead_nodes()));
needs_separator = true;
}
if args.types {
if needs_separator {
println!();
}
print!("{}", q.type_info().dump());
}
return;
unimplemented!();
}

if show_source {
Expand Down
131 changes: 17 additions & 114 deletions crates/plotnik-cli/src/commands/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;

use plotnik_langs::{Lang, NodeFieldId, NodeTypeId};
use plotnik_lib::Query;
use plotnik_lib::engine::interpreter::QueryInterpreter;
use plotnik_lib::engine::validate::validate as validate_result;
use plotnik_lib::engine::value::{ResolvedValue, VerboseResolvedValue};
use plotnik_lib::ir::{NodeKindResolver, QueryEmitter};
use plotnik_lib::QueryBuilder;

use super::debug::source::resolve_lang;

Expand All @@ -23,18 +18,6 @@ pub struct ExecArgs {
pub entry: Option<String>,
}

struct LangResolver(Lang);

impl NodeKindResolver for LangResolver {
fn resolve_kind(&self, name: &str) -> Option<NodeTypeId> {
self.0.resolve_named_node(name)
}

fn resolve_field(&self, name: &str) -> Option<NodeFieldId> {
self.0.resolve_field(name)
}
}

pub fn run(args: ExecArgs) {
if let Err(msg) = validate(&args) {
eprintln!("error: {}", msg);
Expand All @@ -46,113 +29,33 @@ pub fn run(args: ExecArgs) {
eprintln!("error: query cannot be empty");
std::process::exit(1);
}
let source_code = load_source(&args);
let _source_code = load_source(&args);
let lang = resolve_lang(&args.lang, &args.source_text, &args.source_file);

// Parse and validate query
let mut query = Query::new(&query_source).exec().unwrap_or_else(|e| {
// Parse query
let query_parsed = QueryBuilder::new(&query_source)
.parse()
.unwrap_or_else(|e| {
eprintln!("error: {}", e);
std::process::exit(1);
});

// Analyze query
let query_analyzed = query_parsed.analyze().unwrap_or_else(|e| {
eprintln!("error: {}", e);
std::process::exit(1);
});

if !query.is_valid() {
eprint!("{}", query.diagnostics().render(&query_source));
std::process::exit(1);
}

// Link query against language
query.link(&lang);
if !query.is_valid() {
eprint!("{}", query.diagnostics().render(&query_source));
let linked = query_analyzed.link(&lang);
if !linked.is_valid() {
eprint!("{}", linked.diagnostics().render(&query_source));
std::process::exit(1);
}

// Build transition graph and type info
let mut query = query.build_graph();
if query.has_type_errors() {
eprint!("{}", query.diagnostics().render(&query_source));
std::process::exit(1);
}

// Auto-wrap definitions with root node if available
if let Some(root_id) = lang.root()
&& let Some(root_kind) = lang.node_type_name(root_id)
{
query = query.wrap_with_root(root_kind);
}
let _ = (args.pretty, args.verbose_nodes, args.check, args.entry);

// Emit compiled query
let resolver = LangResolver(lang.clone());
let emitter = QueryEmitter::new(query.graph(), query.type_info(), resolver);
let compiled = emitter.emit().unwrap_or_else(|e| {
eprintln!("error: emit failed: {:?}", e);
std::process::exit(1);
});

// Parse source
let tree = lang.parse(&source_code);
let cursor = tree.walk();

// Find entry point
let entrypoint = match &args.entry {
Some(name) => compiled
.entrypoints()
.iter()
.find(|ep| compiled.string(ep.name_id()) == name)
.unwrap_or_else(|| {
let available: Vec<_> = compiled
.entrypoints()
.iter()
.map(|ep| compiled.string(ep.name_id()))
.collect();
eprintln!(
"error: entry point '{}' not found. Available: {}",
name,
available.join(", ")
);
std::process::exit(1);
}),
None => compiled.entrypoints().last().unwrap_or_else(|| {
eprintln!("error: no entry points in query");
std::process::exit(1);
}),
};

// Run interpreter
let interpreter = QueryInterpreter::new(&compiled, cursor, &source_code);
let result = interpreter
.run_from(entrypoint.target())
.unwrap_or_else(|e| {
eprintln!("error: {}", e);
std::process::exit(1);
});

// Type checking against inferred types
if args.check {
let expected_type = Some(entrypoint.result_type());
if let Some(type_id) = expected_type
&& let Err(e) = validate_result(&result, type_id, &compiled)
{
eprintln!("type error: {}", e);
std::process::exit(1);
}
}

// Output JSON
let output = match (args.verbose_nodes, args.pretty) {
(true, true) => serde_json::to_string_pretty(&VerboseResolvedValue(&result, &compiled)),
(true, false) => serde_json::to_string(&VerboseResolvedValue(&result, &compiled)),
(false, true) => serde_json::to_string_pretty(&ResolvedValue(&result, &compiled)),
(false, false) => serde_json::to_string(&ResolvedValue(&result, &compiled)),
};

match output {
Ok(json) => println!("{}", json),
Err(e) => {
eprintln!("error: JSON serialization failed: {}", e);
std::process::exit(1);
}
}
todo!("IR emission and query execution not yet implemented")
}

fn load_query(args: &ExecArgs) -> String {
Expand Down
Loading