Compare commits
No commits in common. "58d2c9b42330018b2cc4022ba9c825542a694492" and "2f21d4ec64d23ac6b597a0e80efce26994c5d427" have entirely different histories.
58d2c9b423
...
2f21d4ec64
54
Cargo.lock
generated
54
Cargo.lock
generated
|
|
@ -2,60 +2,6 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decodal"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"decodal-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decodal-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
|
|
|||
17
Cargo.toml
17
Cargo.toml
|
|
@ -1,13 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/decodal-core",
|
||||
"crates/decodal-cli",
|
||||
]
|
||||
resolver = "3"
|
||||
[package]
|
||||
name = "decodal"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
panic = "abort"
|
||||
[dependencies]
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "decodal"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
regex = ["decodal-core/regex"]
|
||||
|
||||
[dependencies]
|
||||
decodal-core = { path = "../decodal-core" }
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
};
|
||||
|
||||
use decodal_core::{Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceLoader, Span};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
match run() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
print_diagnostic(&error);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Diagnostic> {
|
||||
let mut args = env::args().skip(1);
|
||||
let first = args.next().unwrap_or_else(|| String::from("--help"));
|
||||
match first.as_str() {
|
||||
"--help" | "-h" => {
|
||||
print_help();
|
||||
Ok(())
|
||||
}
|
||||
"--version" | "-V" => {
|
||||
println!("Decodal {}", decodal_core::version());
|
||||
Ok(())
|
||||
}
|
||||
"check" => {
|
||||
let path = args.next().unwrap_or_else(|| String::from("-"));
|
||||
let data = materialize_path(&path)?;
|
||||
drop(data);
|
||||
println!("ok");
|
||||
Ok(())
|
||||
}
|
||||
"eval" | "materialize" => {
|
||||
let path = args.next().unwrap_or_else(|| String::from("-"));
|
||||
let data = materialize_path(&path)?;
|
||||
print_data(&data, 0);
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
path => {
|
||||
let data = materialize_path(path)?;
|
||||
print_data(&data, 0);
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn materialize_path(path: &str) -> Result<Data, Diagnostic> {
|
||||
let root = read_root_source(path)?;
|
||||
let mut engine = Engine::new(FsLoader);
|
||||
let module = engine.add_root_source(root.key, root.name, &root.source)?;
|
||||
let value = engine.eval_module(module)?;
|
||||
engine.materialize(&value)
|
||||
}
|
||||
|
||||
fn read_root_source(path: &str) -> Result<LoadedSource, Diagnostic> {
|
||||
if path == "-" {
|
||||
use std::io::Read;
|
||||
let mut source = String::new();
|
||||
std::io::stdin()
|
||||
.read_to_string(&mut source)
|
||||
.map_err(|error| {
|
||||
Diagnostic::new(
|
||||
DiagnosticKind::Import,
|
||||
Span::default(),
|
||||
format!("failed to read stdin: {error}"),
|
||||
)
|
||||
})?;
|
||||
Ok(LoadedSource {
|
||||
key: String::from("<stdin>"),
|
||||
name: String::from("<stdin>"),
|
||||
source,
|
||||
})
|
||||
} else {
|
||||
load_path(Path::new(path))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct FsLoader;
|
||||
|
||||
impl SourceLoader for FsLoader {
|
||||
fn load(
|
||||
&mut self,
|
||||
current_key: Option<&str>,
|
||||
specifier: &str,
|
||||
) -> Result<LoadedSource, Diagnostic> {
|
||||
let path = Path::new(specifier);
|
||||
let path = if path.is_absolute() {
|
||||
PathBuf::from(path)
|
||||
} else if let Some(current_key) = current_key.filter(|key| *key != "<stdin>") {
|
||||
Path::new(current_key)
|
||||
.parent()
|
||||
.unwrap_or_else(|| Path::new("."))
|
||||
.join(path)
|
||||
} else {
|
||||
PathBuf::from(path)
|
||||
};
|
||||
load_path(&path)
|
||||
}
|
||||
}
|
||||
|
||||
fn load_path(path: &Path) -> Result<LoadedSource, Diagnostic> {
|
||||
let canonical = path.canonicalize().map_err(|error| {
|
||||
Diagnostic::new(
|
||||
DiagnosticKind::Import,
|
||||
Span::default(),
|
||||
format!("failed to resolve `{}`: {error}", path.display()),
|
||||
)
|
||||
})?;
|
||||
let source = fs::read_to_string(&canonical).map_err(|error| {
|
||||
Diagnostic::new(
|
||||
DiagnosticKind::Import,
|
||||
Span::default(),
|
||||
format!("failed to read `{}`: {error}", canonical.display()),
|
||||
)
|
||||
})?;
|
||||
let key = canonical.to_string_lossy().into_owned();
|
||||
Ok(LoadedSource {
|
||||
name: key.clone(),
|
||||
key,
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!("Decodal - Deferred Constraint Data Language");
|
||||
println!();
|
||||
println!("Usage:");
|
||||
println!(" decodal <file.dcdl> Materialize a DCDL file");
|
||||
println!(" decodal eval <file.dcdl> Materialize a DCDL file");
|
||||
println!(" decodal check <file.dcdl> Evaluate and materialize without printing data");
|
||||
println!(" decodal - Read DCDL source from stdin");
|
||||
}
|
||||
|
||||
fn print_diagnostic(error: &Diagnostic) {
|
||||
eprintln!(
|
||||
"error[{kind:?}] {source}:{start}..{end}: {message}",
|
||||
kind = error.kind,
|
||||
source = error.span.source.0,
|
||||
start = error.span.start,
|
||||
end = error.span.end,
|
||||
message = error.message,
|
||||
);
|
||||
}
|
||||
|
||||
fn print_data(data: &Data, indent: usize) {
|
||||
match data {
|
||||
Data::String(value) => print_string(value),
|
||||
Data::Int(value) => print!("{value}"),
|
||||
Data::Float(value) => print!("{value}"),
|
||||
Data::Bool(value) => print!("{value}"),
|
||||
Data::Array(items) => {
|
||||
print!("[");
|
||||
if !items.is_empty() {
|
||||
println!();
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
print_indent(indent + 2);
|
||||
print_data(item, indent + 2);
|
||||
if index + 1 != items.len() {
|
||||
print!(",");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
print_indent(indent);
|
||||
}
|
||||
print!("]");
|
||||
}
|
||||
Data::Object(fields) => {
|
||||
print!("{{");
|
||||
if !fields.is_empty() {
|
||||
println!();
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
print_indent(indent + 2);
|
||||
print_string(&field.name);
|
||||
print!(": ");
|
||||
print_data(&field.value, indent + 2);
|
||||
if index + 1 != fields.len() {
|
||||
print!(",");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
print_indent(indent);
|
||||
}
|
||||
print!("}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_indent(indent: usize) {
|
||||
for _ in 0..indent {
|
||||
print!(" ");
|
||||
}
|
||||
}
|
||||
|
||||
fn print_string(value: &str) {
|
||||
print!("\"");
|
||||
for ch in value.chars() {
|
||||
match ch {
|
||||
'"' => print!("\\\""),
|
||||
'\\' => print!("\\\\"),
|
||||
'\n' => print!("\\n"),
|
||||
'\r' => print!("\\r"),
|
||||
'\t' => print!("\\t"),
|
||||
ch => print!("{ch}"),
|
||||
}
|
||||
}
|
||||
print!("\"");
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
[package]
|
||||
name = "decodal-core"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
regex = ["std", "dep:regex"]
|
||||
|
||||
[dependencies]
|
||||
regex = { version = "1.10", default-features = false, features = ["std", "unicode-perl"], optional = true }
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
use decodal_core::{Data, EmptyLoader, Engine, HostValue};
|
||||
|
||||
fn main() -> decodal_core::Result<()> {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
HostValue::object([
|
||||
("name", HostValue::string_type()),
|
||||
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", HostValue::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
|
||||
let module = engine.add_root_source(
|
||||
"embedded-main",
|
||||
"embedded-main",
|
||||
r#"
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let value = engine.eval_module(module)?;
|
||||
let data = engine.materialize(&value)?;
|
||||
|
||||
if let Data::Object(fields) = data {
|
||||
assert_eq!(fields[0].value, Data::String(String::from("api")));
|
||||
assert_eq!(fields[1].value, Data::Int(9443));
|
||||
assert_eq!(fields[2].value, Data::Bool(true));
|
||||
} else {
|
||||
panic!("expected object");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::span::Span;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ExprId(pub u32);
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Ast {
|
||||
exprs: Vec<SpannedExpr>,
|
||||
}
|
||||
|
||||
impl Ast {
|
||||
pub fn new() -> Self {
|
||||
Self { exprs: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, expr: Expr, span: Span) -> ExprId {
|
||||
let id = ExprId(self.exprs.len() as u32);
|
||||
self.exprs.push(SpannedExpr { expr, span });
|
||||
id
|
||||
}
|
||||
|
||||
pub fn get(&self, id: ExprId) -> &SpannedExpr {
|
||||
&self.exprs[id.0 as usize]
|
||||
}
|
||||
|
||||
pub fn span(&self, id: ExprId) -> Span {
|
||||
self.get(id).span
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.exprs.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.exprs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpannedExpr {
|
||||
pub expr: Expr,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expr {
|
||||
Literal(Literal),
|
||||
Ident(String),
|
||||
Object(Vec<Field>),
|
||||
Array(Vec<ExprId>),
|
||||
Let {
|
||||
bindings: Vec<Field>,
|
||||
body: ExprId,
|
||||
},
|
||||
Import(String),
|
||||
Path {
|
||||
base: ExprId,
|
||||
field: String,
|
||||
},
|
||||
Call {
|
||||
callee: ExprId,
|
||||
args: Vec<ExprId>,
|
||||
},
|
||||
Function {
|
||||
params: Vec<Param>,
|
||||
body: ExprId,
|
||||
},
|
||||
Match {
|
||||
scrutinee: ExprId,
|
||||
arms: Vec<MatchArm>,
|
||||
},
|
||||
Binary {
|
||||
op: BinaryOp,
|
||||
lhs: ExprId,
|
||||
rhs: ExprId,
|
||||
},
|
||||
Default {
|
||||
base: ExprId,
|
||||
fallback: ExprId,
|
||||
},
|
||||
CompareConstraint {
|
||||
op: CompareOp,
|
||||
value: ExprId,
|
||||
},
|
||||
RegexConstraint(String),
|
||||
Wildcard,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Field {
|
||||
pub path: Vec<String>,
|
||||
pub value: ExprId,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub constraint: Option<ExprId>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MatchArm {
|
||||
pub pattern: ExprId,
|
||||
pub body: ExprId,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Literal {
|
||||
String(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BinaryOp {
|
||||
And,
|
||||
Patch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CompareOp {
|
||||
Gt,
|
||||
Gte,
|
||||
Lt,
|
||||
Lte,
|
||||
Eq,
|
||||
}
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{
|
||||
Diagnostic, DiagnosticKind, Span,
|
||||
ast::CompareOp,
|
||||
runtime::{Constraint, LiteralValue, PrimitiveType},
|
||||
};
|
||||
|
||||
pub fn normalize_constraints(
|
||||
constraints: Vec<Constraint>,
|
||||
span: Span,
|
||||
) -> crate::Result<Vec<Constraint>> {
|
||||
let mut primitive = None;
|
||||
let mut lower: Option<Bound> = None;
|
||||
let mut upper: Option<Bound> = None;
|
||||
let mut rest = Vec::new();
|
||||
|
||||
for constraint in constraints {
|
||||
match constraint {
|
||||
Constraint::Type(next) => match primitive {
|
||||
Some(current) if current != next => {
|
||||
return Err(Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
"primitive type constraints conflict",
|
||||
));
|
||||
}
|
||||
Some(_) => {}
|
||||
None => primitive = Some(next),
|
||||
},
|
||||
Constraint::Compare(op, value) => {
|
||||
let number = Number::from_literal(&value).ok_or_else(|| {
|
||||
Diagnostic::new(
|
||||
DiagnosticKind::TypeMismatch,
|
||||
span,
|
||||
"comparison constraints require numeric literals",
|
||||
)
|
||||
})?;
|
||||
match op {
|
||||
CompareOp::Gt => merge_lower(&mut lower, Bound::new(number, false)),
|
||||
CompareOp::Gte => merge_lower(&mut lower, Bound::new(number, true)),
|
||||
CompareOp::Lt => merge_upper(&mut upper, Bound::new(number, false)),
|
||||
CompareOp::Lte => merge_upper(&mut upper, Bound::new(number, true)),
|
||||
CompareOp::Eq => {
|
||||
merge_lower(&mut lower, Bound::new(number, true));
|
||||
merge_upper(&mut upper, Bound::new(number, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
Constraint::Regex(pattern) => rest.push(Constraint::Regex(pattern)),
|
||||
Constraint::BuiltinPredicate(name) => rest.push(Constraint::BuiltinPredicate(name)),
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(primitive, Some(PrimitiveType::String | PrimitiveType::Bool))
|
||||
&& (lower.is_some() || upper.is_some())
|
||||
{
|
||||
return Err(Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
"numeric comparison constraints conflict with non-numeric primitive type",
|
||||
));
|
||||
}
|
||||
|
||||
if primitive == Some(PrimitiveType::Int)
|
||||
&& lower
|
||||
.iter()
|
||||
.chain(upper.iter())
|
||||
.any(|bound| !matches!(bound.number, Number::Int(_)))
|
||||
{
|
||||
return Err(Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
"Int comparison constraints must use integer literals",
|
||||
));
|
||||
}
|
||||
|
||||
ensure_bounds_non_empty(primitive, lower, upper, span)?;
|
||||
|
||||
let mut normalized = Vec::new();
|
||||
if let Some(primitive) = primitive {
|
||||
normalized.push(Constraint::Type(primitive));
|
||||
}
|
||||
if let Some(lower) = lower {
|
||||
normalized.push(Constraint::Compare(
|
||||
if lower.inclusive {
|
||||
CompareOp::Gte
|
||||
} else {
|
||||
CompareOp::Gt
|
||||
},
|
||||
lower.number.into_literal(),
|
||||
));
|
||||
}
|
||||
if let Some(upper) = upper {
|
||||
normalized.push(Constraint::Compare(
|
||||
if upper.inclusive {
|
||||
CompareOp::Lte
|
||||
} else {
|
||||
CompareOp::Lt
|
||||
},
|
||||
upper.number.into_literal(),
|
||||
));
|
||||
}
|
||||
normalized.extend(rest);
|
||||
Ok(normalized)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct Bound {
|
||||
number: Number,
|
||||
inclusive: bool,
|
||||
}
|
||||
|
||||
impl Bound {
|
||||
fn new(number: Number, inclusive: bool) -> Self {
|
||||
Self { number, inclusive }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum Number {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl Number {
|
||||
fn from_literal(value: &LiteralValue) -> Option<Self> {
|
||||
match value {
|
||||
LiteralValue::Int(value) => Some(Self::Int(*value)),
|
||||
LiteralValue::Float(value) => Some(Self::Float(*value)),
|
||||
LiteralValue::String(_) | LiteralValue::Bool(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_literal(self) -> LiteralValue {
|
||||
match self {
|
||||
Self::Int(value) => LiteralValue::Int(value),
|
||||
Self::Float(value) => LiteralValue::Float(value),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_f64(self) -> f64 {
|
||||
match self {
|
||||
Self::Int(value) => value as f64,
|
||||
Self::Float(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_lower(current: &mut Option<Bound>, next: Bound) {
|
||||
match current {
|
||||
None => *current = Some(next),
|
||||
Some(current_bound) if is_stricter_lower(next, *current_bound) => *current_bound = next,
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_upper(current: &mut Option<Bound>, next: Bound) {
|
||||
match current {
|
||||
None => *current = Some(next),
|
||||
Some(current_bound) if is_stricter_upper(next, *current_bound) => *current_bound = next,
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_stricter_lower(next: Bound, current: Bound) -> bool {
|
||||
let next_value = next.number.as_f64();
|
||||
let current_value = current.number.as_f64();
|
||||
next_value > current_value
|
||||
|| (next_value == current_value && !next.inclusive && current.inclusive)
|
||||
}
|
||||
|
||||
fn is_stricter_upper(next: Bound, current: Bound) -> bool {
|
||||
let next_value = next.number.as_f64();
|
||||
let current_value = current.number.as_f64();
|
||||
next_value < current_value
|
||||
|| (next_value == current_value && !next.inclusive && current.inclusive)
|
||||
}
|
||||
|
||||
fn ensure_bounds_non_empty(
|
||||
primitive: Option<PrimitiveType>,
|
||||
lower: Option<Bound>,
|
||||
upper: Option<Bound>,
|
||||
span: Span,
|
||||
) -> crate::Result<()> {
|
||||
if primitive == Some(PrimitiveType::Int) {
|
||||
let min = lower.map(int_lower_bound).unwrap_or(i128::from(i64::MIN));
|
||||
let max = upper.map(int_upper_bound).unwrap_or(i128::from(i64::MAX));
|
||||
if min > max {
|
||||
return Err(empty_numeric_bounds(span));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let (Some(lower), Some(upper)) = (lower, upper) {
|
||||
let lower_value = lower.number.as_f64();
|
||||
let upper_value = upper.number.as_f64();
|
||||
if lower_value > upper_value {
|
||||
return Err(empty_numeric_bounds(span));
|
||||
}
|
||||
if lower_value == upper_value && !(lower.inclusive && upper.inclusive) {
|
||||
return Err(empty_numeric_bounds(span));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn int_lower_bound(bound: Bound) -> i128 {
|
||||
let Number::Int(value) = bound.number else {
|
||||
unreachable!()
|
||||
};
|
||||
if bound.inclusive {
|
||||
i128::from(value)
|
||||
} else {
|
||||
i128::from(value) + 1
|
||||
}
|
||||
}
|
||||
|
||||
fn int_upper_bound(bound: Bound) -> i128 {
|
||||
let Number::Int(value) = bound.number else {
|
||||
unreachable!()
|
||||
};
|
||||
if bound.inclusive {
|
||||
i128::from(value)
|
||||
} else {
|
||||
i128::from(value) - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_numeric_bounds(span: Span) -> Diagnostic {
|
||||
Diagnostic::new(
|
||||
DiagnosticKind::Conflict,
|
||||
span,
|
||||
String::from("numeric comparison constraints have an empty intersection"),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::runtime::LiteralValue;
|
||||
|
||||
#[test]
|
||||
fn detects_primitive_conflict() {
|
||||
assert!(
|
||||
normalize_constraints(
|
||||
alloc::vec![
|
||||
Constraint::Type(PrimitiveType::Int),
|
||||
Constraint::Type(PrimitiveType::String),
|
||||
],
|
||||
Span::default(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_empty_int_range() {
|
||||
assert!(
|
||||
normalize_constraints(
|
||||
alloc::vec![
|
||||
Constraint::Type(PrimitiveType::Int),
|
||||
Constraint::Compare(CompareOp::Gt, LiteralValue::Int(10)),
|
||||
Constraint::Compare(CompareOp::Lt, LiteralValue::Int(11)),
|
||||
],
|
||||
Span::default(),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_regex_constraints_without_intersection_check() {
|
||||
let constraints = normalize_constraints(
|
||||
alloc::vec![
|
||||
Constraint::Regex(String::from("^a$")),
|
||||
Constraint::Regex(String::from("^b$")),
|
||||
],
|
||||
Span::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(constraints.len(), 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
use alloc::string::String;
|
||||
|
||||
use crate::span::Span;
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Diagnostic>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Diagnostic {
|
||||
pub kind: DiagnosticKind,
|
||||
pub span: Span,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
pub fn new(kind: DiagnosticKind, span: Span, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
kind,
|
||||
span,
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syntax(span: Span, message: impl Into<String>) -> Self {
|
||||
Self::new(DiagnosticKind::Syntax, span, message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DiagnosticKind {
|
||||
Syntax,
|
||||
UnresolvedIdentifier,
|
||||
TypeMismatch,
|
||||
ConstraintViolation,
|
||||
Conflict,
|
||||
DefaultConflict,
|
||||
Cycle,
|
||||
Import,
|
||||
MatchFailure,
|
||||
Materialize,
|
||||
UnsupportedFeature,
|
||||
}
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||
|
||||
use crate::runtime::{Constraint, LiteralValue, PrimitiveType};
|
||||
use crate::{CompareOp, Diagnostic, DiagnosticKind, Result, Span};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum HostValue {
|
||||
String(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Array(Vec<HostValue>),
|
||||
Object(Vec<HostField>),
|
||||
Abstract {
|
||||
constraints: Vec<Constraint>,
|
||||
default: Option<Box<HostValue>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HostField {
|
||||
pub name: String,
|
||||
pub value: HostValue,
|
||||
}
|
||||
|
||||
impl HostValue {
|
||||
pub fn string(value: impl Into<String>) -> Self {
|
||||
Self::String(value.into())
|
||||
}
|
||||
|
||||
pub fn int(value: i64) -> Self {
|
||||
Self::Int(value)
|
||||
}
|
||||
|
||||
pub fn float(value: f64) -> Self {
|
||||
Self::Float(value)
|
||||
}
|
||||
|
||||
pub fn bool(value: bool) -> Self {
|
||||
Self::Bool(value)
|
||||
}
|
||||
|
||||
pub fn array<I>(items: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = HostValue>,
|
||||
{
|
||||
Self::Array(items.into_iter().collect())
|
||||
}
|
||||
|
||||
pub fn object<I, N>(fields: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (N, HostValue)>,
|
||||
N: Into<String>,
|
||||
{
|
||||
Self::Object(
|
||||
fields
|
||||
.into_iter()
|
||||
.map(|(name, value)| HostField {
|
||||
name: name.into(),
|
||||
value,
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn string_type() -> Self {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::String))
|
||||
}
|
||||
|
||||
pub fn int_type() -> Self {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Int))
|
||||
}
|
||||
|
||||
pub fn float_type() -> Self {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Float))
|
||||
}
|
||||
|
||||
pub fn bool_type() -> Self {
|
||||
Self::abstract_with_constraint(Constraint::Type(PrimitiveType::Bool))
|
||||
}
|
||||
|
||||
pub fn builtin_predicate(name: impl Into<String>) -> Self {
|
||||
Self::abstract_with_constraint(Constraint::BuiltinPredicate(name.into()))
|
||||
}
|
||||
|
||||
pub fn abstract_with_constraint(constraint: Constraint) -> Self {
|
||||
Self::Abstract {
|
||||
constraints: alloc::vec![constraint],
|
||||
default: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_constraint(mut self, constraint: Constraint) -> Self {
|
||||
match &mut self {
|
||||
Self::Abstract { constraints, .. } => constraints.push(constraint),
|
||||
_ => {
|
||||
self = Self::Abstract {
|
||||
constraints: alloc::vec![constraint],
|
||||
default: Some(Box::new(self)),
|
||||
};
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn gt(self, value: i64) -> Self {
|
||||
self.with_constraint(Constraint::Compare(CompareOp::Gt, LiteralValue::Int(value)))
|
||||
}
|
||||
|
||||
pub fn gte(self, value: i64) -> Self {
|
||||
self.with_constraint(Constraint::Compare(
|
||||
CompareOp::Gte,
|
||||
LiteralValue::Int(value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn lt(self, value: i64) -> Self {
|
||||
self.with_constraint(Constraint::Compare(CompareOp::Lt, LiteralValue::Int(value)))
|
||||
}
|
||||
|
||||
pub fn lte(self, value: i64) -> Self {
|
||||
self.with_constraint(Constraint::Compare(
|
||||
CompareOp::Lte,
|
||||
LiteralValue::Int(value),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn default(self, value: HostValue) -> Result<Self> {
|
||||
match self {
|
||||
Self::Abstract {
|
||||
constraints,
|
||||
default: None,
|
||||
} => Ok(Self::Abstract {
|
||||
constraints,
|
||||
default: Some(Box::new(value)),
|
||||
}),
|
||||
Self::Abstract { .. } => Err(Diagnostic::new(
|
||||
DiagnosticKind::DefaultConflict,
|
||||
Span::default(),
|
||||
"host value already has a default",
|
||||
)),
|
||||
concrete => Ok(Self::Abstract {
|
||||
constraints: Vec::new(),
|
||||
default: Some(Box::new(concrete)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_string(self, value: impl Into<String>) -> Result<Self> {
|
||||
self.default(Self::string(value))
|
||||
}
|
||||
|
||||
pub fn default_int(self, value: i64) -> Result<Self> {
|
||||
self.default(Self::int(value))
|
||||
}
|
||||
|
||||
pub fn default_float(self, value: f64) -> Result<Self> {
|
||||
self.default(Self::float(value))
|
||||
}
|
||||
|
||||
pub fn default_bool(self, value: bool) -> Result<Self> {
|
||||
self.default(Self::bool(value))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,356 +0,0 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{Diagnostic, SourceId, Span, diagnostic::Result};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TokenKind {
|
||||
Ident(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Regex(String),
|
||||
True,
|
||||
False,
|
||||
Let,
|
||||
In,
|
||||
Match,
|
||||
Import,
|
||||
Default,
|
||||
Underscore,
|
||||
LBrace,
|
||||
RBrace,
|
||||
LBracket,
|
||||
RBracket,
|
||||
LParen,
|
||||
RParen,
|
||||
Semicolon,
|
||||
Comma,
|
||||
Dot,
|
||||
Colon,
|
||||
Equal,
|
||||
Arrow,
|
||||
Amp,
|
||||
SlashSlash,
|
||||
Gt,
|
||||
Gte,
|
||||
Lt,
|
||||
Lte,
|
||||
Eof,
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
source_id: SourceId,
|
||||
source: &'a str,
|
||||
bytes: &'a [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(source: &'a str) -> Self {
|
||||
Self::with_source_id(SourceId(0), source)
|
||||
}
|
||||
|
||||
pub fn with_source_id(source_id: SourceId, source: &'a str) -> Self {
|
||||
Self {
|
||||
source_id,
|
||||
source,
|
||||
bytes: source.as_bytes(),
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tokenize(mut self) -> Result<Vec<Token>> {
|
||||
let mut tokens = Vec::new();
|
||||
loop {
|
||||
let token = self.next_token()?;
|
||||
let is_eof = token.kind == TokenKind::Eof;
|
||||
tokens.push(token);
|
||||
if is_eof {
|
||||
return Ok(tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> Result<Token> {
|
||||
self.skip_ws_and_comments();
|
||||
let start = self.pos;
|
||||
let Some(ch) = self.peek() else {
|
||||
return Ok(Token {
|
||||
kind: TokenKind::Eof,
|
||||
span: self.empty_span(self.pos),
|
||||
});
|
||||
};
|
||||
|
||||
let kind = match ch {
|
||||
b'{' => {
|
||||
self.pos += 1;
|
||||
TokenKind::LBrace
|
||||
}
|
||||
b'}' => {
|
||||
self.pos += 1;
|
||||
TokenKind::RBrace
|
||||
}
|
||||
b'[' => {
|
||||
self.pos += 1;
|
||||
TokenKind::LBracket
|
||||
}
|
||||
b']' => {
|
||||
self.pos += 1;
|
||||
TokenKind::RBracket
|
||||
}
|
||||
b'(' => {
|
||||
self.pos += 1;
|
||||
TokenKind::LParen
|
||||
}
|
||||
b')' => {
|
||||
self.pos += 1;
|
||||
TokenKind::RParen
|
||||
}
|
||||
b';' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Semicolon
|
||||
}
|
||||
b',' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Comma
|
||||
}
|
||||
b'.' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Dot
|
||||
}
|
||||
b':' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Colon
|
||||
}
|
||||
b'_' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Underscore
|
||||
}
|
||||
b'&' => {
|
||||
self.pos += 1;
|
||||
TokenKind::Amp
|
||||
}
|
||||
b'=' => {
|
||||
self.pos += 1;
|
||||
if self.consume(b'>') {
|
||||
TokenKind::Arrow
|
||||
} else {
|
||||
TokenKind::Equal
|
||||
}
|
||||
}
|
||||
b'>' => {
|
||||
self.pos += 1;
|
||||
if self.consume(b'=') {
|
||||
TokenKind::Gte
|
||||
} else {
|
||||
TokenKind::Gt
|
||||
}
|
||||
}
|
||||
b'<' => {
|
||||
self.pos += 1;
|
||||
if self.consume(b'=') {
|
||||
TokenKind::Lte
|
||||
} else {
|
||||
TokenKind::Lt
|
||||
}
|
||||
}
|
||||
b'/' => {
|
||||
self.pos += 1;
|
||||
if self.consume(b'/') {
|
||||
TokenKind::SlashSlash
|
||||
} else {
|
||||
self.lex_regex(start)?
|
||||
}
|
||||
}
|
||||
b'"' => self.lex_string()?,
|
||||
b'0'..=b'9' => self.lex_number()?,
|
||||
c if is_ident_start(c) => self.lex_ident_or_keyword(),
|
||||
_ => {
|
||||
return Err(Diagnostic::syntax(
|
||||
self.span(start, start + 1),
|
||||
"unexpected character",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Token {
|
||||
kind,
|
||||
span: self.span(start, self.pos),
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_ws_and_comments(&mut self) {
|
||||
loop {
|
||||
while matches!(self.peek(), Some(b' ' | b'\t' | b'\r' | b'\n')) {
|
||||
self.pos += 1;
|
||||
}
|
||||
if self.peek() == Some(b'#') {
|
||||
while let Some(c) = self.peek() {
|
||||
self.pos += 1;
|
||||
if c == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fn lex_string(&mut self) -> Result<TokenKind> {
|
||||
let start = self.pos;
|
||||
self.pos += 1;
|
||||
let mut value = String::new();
|
||||
while let Some(c) = self.peek() {
|
||||
self.pos += 1;
|
||||
match c {
|
||||
b'"' => return Ok(TokenKind::String(value)),
|
||||
b'\\' => {
|
||||
let Some(escaped) = self.peek() else {
|
||||
return Err(Diagnostic::syntax(
|
||||
self.span(start, self.pos),
|
||||
"unterminated escape",
|
||||
));
|
||||
};
|
||||
self.pos += 1;
|
||||
let ch = match escaped {
|
||||
b'"' => '"',
|
||||
b'\\' => '\\',
|
||||
b'n' => '\n',
|
||||
b'r' => '\r',
|
||||
b't' => '\t',
|
||||
other => other as char,
|
||||
};
|
||||
value.push(ch);
|
||||
}
|
||||
other => value.push(other as char),
|
||||
}
|
||||
}
|
||||
Err(Diagnostic::syntax(
|
||||
self.span(start, self.pos),
|
||||
"unterminated string",
|
||||
))
|
||||
}
|
||||
|
||||
fn lex_regex(&mut self, start: usize) -> Result<TokenKind> {
|
||||
let mut pattern = String::new();
|
||||
let mut escaped = false;
|
||||
while let Some(c) = self.peek() {
|
||||
self.pos += 1;
|
||||
if escaped {
|
||||
pattern.push(c as char);
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
match c {
|
||||
b'\\' => {
|
||||
pattern.push('\\');
|
||||
escaped = true;
|
||||
}
|
||||
b'/' => return Ok(TokenKind::Regex(pattern)),
|
||||
other => pattern.push(other as char),
|
||||
}
|
||||
}
|
||||
Err(Diagnostic::syntax(
|
||||
self.span(start, self.pos),
|
||||
"unterminated regex",
|
||||
))
|
||||
}
|
||||
|
||||
fn lex_number(&mut self) -> Result<TokenKind> {
|
||||
let start = self.pos;
|
||||
while matches!(self.peek(), Some(b'0'..=b'9')) {
|
||||
self.pos += 1;
|
||||
}
|
||||
let mut is_float = false;
|
||||
if self.peek() == Some(b'.') && matches!(self.peek_n(1), Some(b'0'..=b'9')) {
|
||||
is_float = true;
|
||||
self.pos += 1;
|
||||
while matches!(self.peek(), Some(b'0'..=b'9')) {
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
let text = &self.source[start..self.pos];
|
||||
if is_float {
|
||||
text.parse::<f64>().map(TokenKind::Float).map_err(|_| {
|
||||
Diagnostic::syntax(self.span(start, self.pos), "invalid float literal")
|
||||
})
|
||||
} else {
|
||||
text.parse::<i64>()
|
||||
.map(TokenKind::Int)
|
||||
.map_err(|_| Diagnostic::syntax(self.span(start, self.pos), "invalid int literal"))
|
||||
}
|
||||
}
|
||||
|
||||
fn lex_ident_or_keyword(&mut self) -> TokenKind {
|
||||
let start = self.pos;
|
||||
self.pos += 1;
|
||||
while matches!(self.peek(), Some(c) if is_ident_continue(c)) {
|
||||
self.pos += 1;
|
||||
}
|
||||
let text = &self.source[start..self.pos];
|
||||
match text {
|
||||
"true" => TokenKind::True,
|
||||
"false" => TokenKind::False,
|
||||
"let" => TokenKind::Let,
|
||||
"in" => TokenKind::In,
|
||||
"match" => TokenKind::Match,
|
||||
"import" => TokenKind::Import,
|
||||
"default" => TokenKind::Default,
|
||||
_ => TokenKind::Ident(String::from(text)),
|
||||
}
|
||||
}
|
||||
|
||||
fn span(&self, start: usize, end: usize) -> Span {
|
||||
Span::new(self.source_id, start, end)
|
||||
}
|
||||
|
||||
fn empty_span(&self, offset: usize) -> Span {
|
||||
Span::empty(self.source_id, offset)
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<u8> {
|
||||
self.bytes.get(self.pos).copied()
|
||||
}
|
||||
|
||||
fn peek_n(&self, n: usize) -> Option<u8> {
|
||||
self.bytes.get(self.pos + n).copied()
|
||||
}
|
||||
|
||||
fn consume(&mut self, expected: u8) -> bool {
|
||||
if self.peek() == Some(expected) {
|
||||
self.pos += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ident_start(c: u8) -> bool {
|
||||
c.is_ascii_alphabetic()
|
||||
}
|
||||
|
||||
fn is_ident_continue(c: u8) -> bool {
|
||||
c.is_ascii_alphanumeric() || c == b'_'
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn tokenizes_basic_source() {
|
||||
let tokens = Lexer::new("port = Int & >= 1;").tokenize().unwrap();
|
||||
assert!(matches!(tokens[0].kind, TokenKind::Ident(_)));
|
||||
assert_eq!(tokens[1].kind, TokenKind::Equal);
|
||||
assert_eq!(tokens[3].kind, TokenKind::Amp);
|
||||
assert_eq!(tokens[4].kind, TokenKind::Gte);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod ast;
|
||||
pub mod constraints;
|
||||
pub mod diagnostic;
|
||||
pub mod embedding;
|
||||
pub mod eval;
|
||||
pub mod lexer;
|
||||
pub mod module;
|
||||
pub mod parser;
|
||||
pub mod runtime;
|
||||
pub mod span;
|
||||
|
||||
pub use ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, Param};
|
||||
pub use constraints::normalize_constraints;
|
||||
pub use diagnostic::{Diagnostic, DiagnosticKind, Result};
|
||||
pub use embedding::{HostField, HostValue};
|
||||
pub use eval::Engine;
|
||||
pub use lexer::{Lexer, Token, TokenKind};
|
||||
pub use module::{EmptyLoader, LoadedSource, Module, SourceLoader};
|
||||
pub use parser::{ParseOutput, Parser, SourceForm, parse_source, parse_source_with_source_id};
|
||||
pub use runtime::{Constraint, Data, ExprRef, LiteralValue, ModuleId, PrimitiveType, RuntimeValue};
|
||||
pub use span::{SourceId, Span};
|
||||
|
||||
pub fn version() -> &'static str {
|
||||
env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
use alloc::string::String;
|
||||
|
||||
use crate::{
|
||||
Ast, ExprId, SourceForm, SourceId,
|
||||
runtime::{EnvId, ThunkId},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Module {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub source: SourceId,
|
||||
pub ast: Ast,
|
||||
pub root: ExprId,
|
||||
pub source_form: SourceForm,
|
||||
pub root_env: EnvId,
|
||||
pub root_thunk: ThunkId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadedSource {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
pub trait SourceLoader {
|
||||
fn load(&mut self, current_key: Option<&str>, specifier: &str) -> crate::Result<LoadedSource>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct EmptyLoader;
|
||||
|
||||
impl SourceLoader for EmptyLoader {
|
||||
fn load(&mut self, _current_key: Option<&str>, specifier: &str) -> crate::Result<LoadedSource> {
|
||||
Err(crate::Diagnostic::new(
|
||||
crate::DiagnosticKind::Import,
|
||||
crate::Span::default(),
|
||||
alloc::format!("no source loader is configured for import `{specifier}`"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,525 +0,0 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{
|
||||
SourceId, Span,
|
||||
ast::{Ast, BinaryOp, CompareOp, Expr, ExprId, Field, Literal, MatchArm, Param},
|
||||
diagnostic::{Diagnostic, Result},
|
||||
lexer::{Lexer, Token, TokenKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseOutput {
|
||||
pub ast: Ast,
|
||||
pub root: ExprId,
|
||||
pub source_form: SourceForm,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SourceForm {
|
||||
Expr,
|
||||
Fields,
|
||||
}
|
||||
|
||||
pub fn parse_source(source: &str) -> Result<ParseOutput> {
|
||||
parse_source_with_source_id(SourceId(0), source)
|
||||
}
|
||||
|
||||
pub fn parse_source_with_source_id(source_id: SourceId, source: &str) -> Result<ParseOutput> {
|
||||
let tokens = Lexer::with_source_id(source_id, source).tokenize()?;
|
||||
Parser::new(tokens).parse()
|
||||
}
|
||||
|
||||
pub struct Parser {
|
||||
tokens: Vec<Token>,
|
||||
pos: usize,
|
||||
ast: Ast,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(tokens: Vec<Token>) -> Self {
|
||||
Self {
|
||||
tokens,
|
||||
pos: 0,
|
||||
ast: Ast::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(mut self) -> Result<ParseOutput> {
|
||||
let (root, source_form) = if self.starts_field() {
|
||||
let fields = self.parse_fields_until_eof()?;
|
||||
let span = fields
|
||||
.first()
|
||||
.map(|f| {
|
||||
fields
|
||||
.iter()
|
||||
.fold(f.span, |acc, field| acc.join(field.span))
|
||||
})
|
||||
.unwrap_or_else(|| self.peek().span);
|
||||
(
|
||||
self.ast.push(Expr::Object(fields), span),
|
||||
SourceForm::Fields,
|
||||
)
|
||||
} else {
|
||||
let expr = self.parse_expr(0)?;
|
||||
self.expect_eof()?;
|
||||
(expr, SourceForm::Expr)
|
||||
};
|
||||
Ok(ParseOutput {
|
||||
ast: self.ast,
|
||||
root,
|
||||
source_form,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_expr(&mut self, min_bp: u8) -> Result<ExprId> {
|
||||
let mut lhs = self.parse_prefix()?;
|
||||
|
||||
loop {
|
||||
if self.at_eof() || self.is_expr_stop() {
|
||||
break;
|
||||
}
|
||||
|
||||
// postfix: path reference
|
||||
if self.consume_kind(&TokenKind::Dot).is_some() {
|
||||
let (field, field_span) = self.expect_ident()?;
|
||||
let span = self.ast.span(lhs).join(field_span);
|
||||
lhs = self.ast.push(Expr::Path { base: lhs, field }, span);
|
||||
continue;
|
||||
}
|
||||
|
||||
// postfix: call
|
||||
if self.consume_kind(&TokenKind::LParen).is_some() {
|
||||
let mut args = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RParen).is_none() {
|
||||
loop {
|
||||
args.push(self.parse_expr(0)?);
|
||||
if self.consume_kind(&TokenKind::Comma).is_some() {
|
||||
continue;
|
||||
}
|
||||
self.expect_kind(&TokenKind::RParen, "expected ')' after call arguments")?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let span = self.ast.span(lhs).join(self.previous_span());
|
||||
lhs = self.ast.push(Expr::Call { callee: lhs, args }, span);
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some((kind, l_bp, r_bp)) = self.peek_infix() else {
|
||||
break;
|
||||
};
|
||||
if l_bp < min_bp {
|
||||
break;
|
||||
}
|
||||
let op_span = self.advance().span;
|
||||
let rhs = self.parse_expr(r_bp)?;
|
||||
let span = self.ast.span(lhs).join(self.ast.span(rhs)).join(op_span);
|
||||
lhs = match kind {
|
||||
InfixKind::And => self.ast.push(
|
||||
Expr::Binary {
|
||||
op: BinaryOp::And,
|
||||
lhs,
|
||||
rhs,
|
||||
},
|
||||
span,
|
||||
),
|
||||
InfixKind::Patch => self.ast.push(
|
||||
Expr::Binary {
|
||||
op: BinaryOp::Patch,
|
||||
lhs,
|
||||
rhs,
|
||||
},
|
||||
span,
|
||||
),
|
||||
InfixKind::Default => self.ast.push(
|
||||
Expr::Default {
|
||||
base: lhs,
|
||||
fallback: rhs,
|
||||
},
|
||||
span,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(lhs)
|
||||
}
|
||||
|
||||
fn parse_prefix(&mut self) -> Result<ExprId> {
|
||||
let token = self.advance().clone();
|
||||
match token.kind {
|
||||
TokenKind::String(value) => Ok(self
|
||||
.ast
|
||||
.push(Expr::Literal(Literal::String(value)), token.span)),
|
||||
TokenKind::Int(value) => Ok(self
|
||||
.ast
|
||||
.push(Expr::Literal(Literal::Int(value)), token.span)),
|
||||
TokenKind::Float(value) => Ok(self
|
||||
.ast
|
||||
.push(Expr::Literal(Literal::Float(value)), token.span)),
|
||||
TokenKind::True => Ok(self
|
||||
.ast
|
||||
.push(Expr::Literal(Literal::Bool(true)), token.span)),
|
||||
TokenKind::False => Ok(self
|
||||
.ast
|
||||
.push(Expr::Literal(Literal::Bool(false)), token.span)),
|
||||
TokenKind::Ident(name) => Ok(self.ast.push(Expr::Ident(name), token.span)),
|
||||
TokenKind::Regex(pattern) => {
|
||||
Ok(self.ast.push(Expr::RegexConstraint(pattern), token.span))
|
||||
}
|
||||
TokenKind::Underscore => Ok(self.ast.push(Expr::Wildcard, token.span)),
|
||||
TokenKind::LBrace => self.parse_object_after_lbrace(token.span),
|
||||
TokenKind::LBracket => self.parse_array_after_lbracket(token.span),
|
||||
TokenKind::LParen => self.parse_group_or_function(token.span),
|
||||
TokenKind::Let => self.parse_let(token.span),
|
||||
TokenKind::Match => self.parse_match(token.span),
|
||||
TokenKind::Import => self.parse_import(token.span),
|
||||
TokenKind::Gt | TokenKind::Gte | TokenKind::Lt | TokenKind::Lte => {
|
||||
let op = match token.kind {
|
||||
TokenKind::Gt => CompareOp::Gt,
|
||||
TokenKind::Gte => CompareOp::Gte,
|
||||
TokenKind::Lt => CompareOp::Lt,
|
||||
TokenKind::Lte => CompareOp::Lte,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let value = self.parse_expr(8)?;
|
||||
let span = token.span.join(self.ast.span(value));
|
||||
Ok(self.ast.push(Expr::CompareConstraint { op, value }, span))
|
||||
}
|
||||
_ => Err(Diagnostic::syntax(token.span, "expected expression")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_object_after_lbrace(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
let mut fields = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RBrace).is_some() {
|
||||
return Ok(self
|
||||
.ast
|
||||
.push(Expr::Object(fields), start_span.join(self.previous_span())));
|
||||
}
|
||||
loop {
|
||||
fields.push(self.parse_field()?);
|
||||
if self.consume_kind(&TokenKind::Semicolon).is_some() {
|
||||
if self.consume_kind(&TokenKind::RBrace).is_some() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
self.expect_kind(&TokenKind::RBrace, "expected ';' or '}' after object field")?;
|
||||
break;
|
||||
}
|
||||
let span = start_span.join(self.previous_span());
|
||||
Ok(self.ast.push(Expr::Object(fields), span))
|
||||
}
|
||||
|
||||
fn parse_array_after_lbracket(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
let mut items = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RBracket).is_some() {
|
||||
return Ok(self
|
||||
.ast
|
||||
.push(Expr::Array(items), start_span.join(self.previous_span())));
|
||||
}
|
||||
loop {
|
||||
items.push(self.parse_expr(0)?);
|
||||
if self.consume_kind(&TokenKind::Comma).is_some() {
|
||||
if self.consume_kind(&TokenKind::RBracket).is_some() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
self.expect_kind(&TokenKind::RBracket, "expected ',' or ']' after array item")?;
|
||||
break;
|
||||
}
|
||||
let span = start_span.join(self.previous_span());
|
||||
Ok(self.ast.push(Expr::Array(items), span))
|
||||
}
|
||||
|
||||
fn parse_group_or_function(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
if self.looks_like_params() {
|
||||
let params = self.parse_params_after_lparen()?;
|
||||
self.expect_kind(&TokenKind::Arrow, "expected '=>' after function parameters")?;
|
||||
let body = self.parse_expr(0)?;
|
||||
let span = start_span.join(self.ast.span(body));
|
||||
return Ok(self.ast.push(Expr::Function { params, body }, span));
|
||||
}
|
||||
|
||||
let expr = self.parse_expr(0)?;
|
||||
self.expect_kind(&TokenKind::RParen, "expected ')' after expression")?;
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_params_after_lparen(&mut self) -> Result<Vec<Param>> {
|
||||
let mut params = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RParen).is_some() {
|
||||
return Ok(params);
|
||||
}
|
||||
loop {
|
||||
let (name, name_span) = self.expect_ident()?;
|
||||
let constraint = if self.consume_kind(&TokenKind::Colon).is_some() {
|
||||
Some(self.parse_expr(0)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let span = constraint
|
||||
.map(|id| name_span.join(self.ast.span(id)))
|
||||
.unwrap_or(name_span);
|
||||
params.push(Param {
|
||||
name,
|
||||
constraint,
|
||||
span,
|
||||
});
|
||||
if self.consume_kind(&TokenKind::Comma).is_some() {
|
||||
continue;
|
||||
}
|
||||
self.expect_kind(&TokenKind::RParen, "expected ',' or ')' after parameter")?;
|
||||
break;
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn parse_let(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
let mut bindings = Vec::new();
|
||||
while !self.check_kind(&TokenKind::In) && !self.at_eof() {
|
||||
bindings.push(self.parse_field()?);
|
||||
self.expect_kind(&TokenKind::Semicolon, "expected ';' after let binding")?;
|
||||
}
|
||||
self.expect_kind(&TokenKind::In, "expected 'in' after let bindings")?;
|
||||
let body = self.parse_expr(0)?;
|
||||
let span = start_span.join(self.ast.span(body));
|
||||
Ok(self.ast.push(Expr::Let { bindings, body }, span))
|
||||
}
|
||||
|
||||
fn parse_match(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
let scrutinee = self.parse_expr(0)?;
|
||||
self.expect_kind(&TokenKind::LBrace, "expected '{' after match scrutinee")?;
|
||||
let mut arms = Vec::new();
|
||||
if self.consume_kind(&TokenKind::RBrace).is_none() {
|
||||
loop {
|
||||
let pattern = self.parse_expr(0)?;
|
||||
self.expect_kind(&TokenKind::Colon, "expected ':' after match pattern")?;
|
||||
let body = self.parse_expr(0)?;
|
||||
let span = self.ast.span(pattern).join(self.ast.span(body));
|
||||
arms.push(MatchArm {
|
||||
pattern,
|
||||
body,
|
||||
span,
|
||||
});
|
||||
if self.consume_kind(&TokenKind::Semicolon).is_some() {
|
||||
if self.consume_kind(&TokenKind::RBrace).is_some() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
self.expect_kind(&TokenKind::RBrace, "expected ';' or '}' after match arm")?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let span = start_span.join(self.previous_span());
|
||||
Ok(self.ast.push(Expr::Match { scrutinee, arms }, span))
|
||||
}
|
||||
|
||||
fn parse_import(&mut self, start_span: Span) -> Result<ExprId> {
|
||||
let token = self.advance().clone();
|
||||
let path = match token.kind {
|
||||
TokenKind::String(path) | TokenKind::Ident(path) => path,
|
||||
_ => return Err(Diagnostic::syntax(token.span, "expected import path")),
|
||||
};
|
||||
Ok(self
|
||||
.ast
|
||||
.push(Expr::Import(path), start_span.join(token.span)))
|
||||
}
|
||||
|
||||
fn parse_fields_until_eof(&mut self) -> Result<Vec<Field>> {
|
||||
let mut fields = Vec::new();
|
||||
while !self.at_eof() {
|
||||
fields.push(self.parse_field()?);
|
||||
self.consume_kind(&TokenKind::Semicolon);
|
||||
}
|
||||
Ok(fields)
|
||||
}
|
||||
|
||||
fn parse_field(&mut self) -> Result<Field> {
|
||||
let (first, first_span) = self.expect_ident()?;
|
||||
let mut path = Vec::new();
|
||||
path.push(first);
|
||||
let mut span = first_span;
|
||||
while self.consume_kind(&TokenKind::Dot).is_some() {
|
||||
let (name, name_span) = self.expect_ident()?;
|
||||
span = span.join(name_span);
|
||||
path.push(name);
|
||||
}
|
||||
self.expect_kind(&TokenKind::Equal, "expected '=' after field name")?;
|
||||
let value = self.parse_expr(0)?;
|
||||
span = span.join(self.ast.span(value));
|
||||
Ok(Field { path, value, span })
|
||||
}
|
||||
|
||||
fn starts_field(&self) -> bool {
|
||||
matches!(self.peek_kind(), TokenKind::Ident(_))
|
||||
&& matches!(self.peek_kind_n(1), TokenKind::Equal | TokenKind::Dot)
|
||||
}
|
||||
|
||||
fn looks_like_params(&self) -> bool {
|
||||
match self.peek_kind() {
|
||||
TokenKind::RParen => matches!(self.peek_kind_n(1), TokenKind::Arrow),
|
||||
TokenKind::Ident(_) => {
|
||||
let mut i = self.pos;
|
||||
loop {
|
||||
if !matches!(self.kind_at(i), TokenKind::Ident(_)) {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
if matches!(self.kind_at(i), TokenKind::Colon) {
|
||||
// Skip a simple constraint expression approximately until comma/rparen.
|
||||
i += 1;
|
||||
let mut depth = 0usize;
|
||||
while !matches!(self.kind_at(i), TokenKind::Eof) {
|
||||
match self.kind_at(i) {
|
||||
TokenKind::LParen | TokenKind::LBrace | TokenKind::LBracket => {
|
||||
depth += 1
|
||||
}
|
||||
TokenKind::RParen if depth == 0 => break,
|
||||
TokenKind::RParen | TokenKind::RBrace | TokenKind::RBracket => {
|
||||
depth = depth.saturating_sub(1)
|
||||
}
|
||||
TokenKind::Comma if depth == 0 => break,
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if matches!(self.kind_at(i), TokenKind::Comma) {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if matches!(self.kind_at(i), TokenKind::RParen) {
|
||||
return matches!(self.kind_at(i + 1), TokenKind::Arrow);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_infix(&self) -> Option<(InfixKind, u8, u8)> {
|
||||
match self.peek_kind() {
|
||||
TokenKind::Default => Some((InfixKind::Default, 1, 2)),
|
||||
TokenKind::SlashSlash => Some((InfixKind::Patch, 3, 4)),
|
||||
TokenKind::Amp => Some((InfixKind::And, 5, 6)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expr_stop(&self) -> bool {
|
||||
matches!(
|
||||
self.peek_kind(),
|
||||
TokenKind::Semicolon
|
||||
| TokenKind::Comma
|
||||
| TokenKind::RParen
|
||||
| TokenKind::RBracket
|
||||
| TokenKind::RBrace
|
||||
| TokenKind::Colon
|
||||
| TokenKind::In
|
||||
| TokenKind::Arrow
|
||||
)
|
||||
}
|
||||
|
||||
fn expect_ident(&mut self) -> Result<(String, Span)> {
|
||||
let token = self.advance().clone();
|
||||
match token.kind {
|
||||
TokenKind::Ident(name) => Ok((name, token.span)),
|
||||
_ => Err(Diagnostic::syntax(token.span, "expected identifier")),
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_kind(&mut self, expected: &TokenKind, message: &'static str) -> Result<Span> {
|
||||
if let Some(span) = self.consume_kind(expected) {
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(Diagnostic::syntax(self.peek().span, message))
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_eof(&mut self) -> Result<()> {
|
||||
if self.at_eof() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Diagnostic::syntax(self.peek().span, "expected end of file"))
|
||||
}
|
||||
}
|
||||
|
||||
fn consume_kind(&mut self, expected: &TokenKind) -> Option<Span> {
|
||||
if core::mem::discriminant(self.peek_kind()) == core::mem::discriminant(expected) {
|
||||
Some(self.advance().span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn check_kind(&self, expected: &TokenKind) -> bool {
|
||||
core::mem::discriminant(self.peek_kind()) == core::mem::discriminant(expected)
|
||||
}
|
||||
|
||||
fn at_eof(&self) -> bool {
|
||||
matches!(self.peek_kind(), TokenKind::Eof)
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Token {
|
||||
let index = self.pos;
|
||||
if !self.at_eof() {
|
||||
self.pos += 1;
|
||||
}
|
||||
&self.tokens[index]
|
||||
}
|
||||
|
||||
fn previous_span(&self) -> Span {
|
||||
self.tokens[self.pos.saturating_sub(1)].span
|
||||
}
|
||||
|
||||
fn peek(&self) -> &Token {
|
||||
&self.tokens[self.pos]
|
||||
}
|
||||
|
||||
fn peek_kind(&self) -> &TokenKind {
|
||||
&self.peek().kind
|
||||
}
|
||||
|
||||
fn peek_kind_n(&self, n: usize) -> &TokenKind {
|
||||
self.kind_at(self.pos + n)
|
||||
}
|
||||
|
||||
fn kind_at(&self, index: usize) -> &TokenKind {
|
||||
self.tokens
|
||||
.get(index)
|
||||
.map(|token| &token.kind)
|
||||
.unwrap_or(&TokenKind::Eof)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum InfixKind {
|
||||
And,
|
||||
Patch,
|
||||
Default,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Expr;
|
||||
|
||||
#[test]
|
||||
fn parses_top_level_fields_as_object() {
|
||||
let parsed = parse_source("port = Int & >= 1 default 8080;").unwrap();
|
||||
assert!(matches!(parsed.ast.get(parsed.root).expr, Expr::Object(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_object_dot_field() {
|
||||
let parsed = parse_source("{ feature.enable = false; }").unwrap();
|
||||
let Expr::Object(fields) = &parsed.ast.get(parsed.root).expr else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(fields[0].path, ["feature", "enable"]);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
use alloc::{string::String, vec::Vec};
|
||||
|
||||
use crate::{ExprId, ast::CompareOp};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ModuleId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ExprRef {
|
||||
pub module: ModuleId,
|
||||
pub expr: ExprId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RuntimeValue {
|
||||
Concrete(ConcreteValue),
|
||||
Abstract(AbstractValue),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConcreteValue {
|
||||
String(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Array(Vec<ThunkId>),
|
||||
Object(ObjectValue),
|
||||
Function(FunctionValue),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ObjectValue {
|
||||
pub fields: Vec<ObjectField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ObjectField {
|
||||
pub name: String,
|
||||
pub value: ThunkId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FunctionValue {
|
||||
pub params: Vec<FunctionParam>,
|
||||
pub body: ExprRef,
|
||||
pub env: EnvId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FunctionParam {
|
||||
pub name: String,
|
||||
pub constraint: Option<ExprRef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct AbstractValue {
|
||||
pub constraints: Vec<Constraint>,
|
||||
pub default: Option<ThunkId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Constraint {
|
||||
Type(PrimitiveType),
|
||||
Compare(CompareOp, LiteralValue),
|
||||
Regex(String),
|
||||
BuiltinPredicate(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum PrimitiveType {
|
||||
String,
|
||||
Int,
|
||||
Float,
|
||||
Bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LiteralValue {
|
||||
String(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Data {
|
||||
String(String),
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Array(Vec<Data>),
|
||||
Object(Vec<DataField>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DataField {
|
||||
pub name: String,
|
||||
pub value: Data,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ThunkId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct EnvId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Thunk {
|
||||
pub kind: ThunkKind,
|
||||
pub state: ThunkState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThunkKind {
|
||||
Expr {
|
||||
expr: ExprRef,
|
||||
env: EnvId,
|
||||
},
|
||||
Constrained {
|
||||
constraint: ExprRef,
|
||||
constraint_env: EnvId,
|
||||
value: ExprRef,
|
||||
value_env: EnvId,
|
||||
},
|
||||
Value(RuntimeValue),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThunkState {
|
||||
Unevaluated,
|
||||
Evaluating,
|
||||
Evaluated(RuntimeValue),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Env {
|
||||
pub parent: Option<EnvId>,
|
||||
pub bindings: Vec<Binding>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Binding {
|
||||
pub name: String,
|
||||
pub value: ThunkId,
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
pub struct SourceId(pub u32);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Span {
|
||||
pub source: SourceId,
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
impl Span {
|
||||
pub const fn new(source: SourceId, start: usize, end: usize) -> Self {
|
||||
Self {
|
||||
source,
|
||||
start: start as u32,
|
||||
end: end as u32,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn empty(source: SourceId, offset: usize) -> Self {
|
||||
Self::new(source, offset, offset)
|
||||
}
|
||||
|
||||
pub fn join(self, other: Self) -> Self {
|
||||
Self {
|
||||
source: self.source,
|
||||
start: self.start.min(other.start),
|
||||
end: self.end.max(other.end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,9 @@ pkgs.mkShell {
|
|||
cargo
|
||||
clippy
|
||||
git
|
||||
nodejs
|
||||
nixfmt
|
||||
rustc
|
||||
rustfmt
|
||||
tree-sitter
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
# Embedding API
|
||||
|
||||
Decodal core can be embedded without giving the core crate access to a filesystem.
|
||||
The host supplies imported sources through `SourceLoader` and may also provide global bindings through the host prelude API.
|
||||
|
||||
## Host prelude
|
||||
|
||||
`Engine` owns a prelude environment.
|
||||
Bindings in this environment are visible from every module loaded by the engine.
|
||||
|
||||
```text
|
||||
prelude env
|
||||
↓
|
||||
module root env
|
||||
↓
|
||||
let / function env
|
||||
```
|
||||
|
||||
Module top-level bindings shadow prelude bindings.
|
||||
Primitive type names such as `String`, `Int`, `Float`, and `Bool` are handled before environment lookup, so they are reserved and cannot be shadowed by host bindings.
|
||||
|
||||
## Global bindings
|
||||
|
||||
The host can bind values before adding or evaluating user sources.
|
||||
|
||||
```rust
|
||||
use decodal_core::{EmptyLoader, Engine, HostValue};
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
HostValue::object([
|
||||
("name", HostValue::string_type()),
|
||||
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", HostValue::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
```
|
||||
|
||||
A user source can then refer to `Service` without importing it.
|
||||
|
||||
```dcdl
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
```
|
||||
|
||||
## HostValue
|
||||
|
||||
`HostValue` is the public builder-facing value representation for embedding.
|
||||
It keeps host code from constructing internal `ThunkId` or `ObjectValue` values directly.
|
||||
|
||||
```text
|
||||
HostValue =
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
Array(Vec<HostValue>)
|
||||
Object(Vec<HostField>)
|
||||
Abstract { constraints, default }
|
||||
```
|
||||
|
||||
When a host value is bound, the engine internalizes it into `RuntimeValue` and allocates value thunks for object fields, array items, and defaults.
|
||||
|
||||
## Abstract host objects
|
||||
|
||||
A host-provided schema object is represented as a concrete object structure whose fields may contain abstract values.
|
||||
|
||||
```rust
|
||||
HostValue::object([
|
||||
("name", HostValue::string_type()),
|
||||
("port", HostValue::int_type().gt(443).default_int(8443)?),
|
||||
])
|
||||
```
|
||||
|
||||
Conceptually this becomes:
|
||||
|
||||
```text
|
||||
Concrete(Object {
|
||||
name -> Thunk(Abstract { constraints: [String], default: none })
|
||||
port -> Thunk(Abstract { constraints: [Int, > 443], default: 8443 })
|
||||
})
|
||||
```
|
||||
|
||||
This matches the runtime model used for Decodal source-defined schema objects.
|
||||
|
||||
## SourceLoader and prelude together
|
||||
|
||||
`SourceLoader` and host prelude bindings are independent mechanisms.
|
||||
|
||||
- Use `SourceLoader` when user sources should explicitly import host-provided modules.
|
||||
- Use prelude bindings when host-provided schemas or constants should be globally available.
|
||||
|
||||
Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.
|
||||
|
|
@ -54,30 +54,23 @@ desugar は、意味論を単純にするための表層構文変換を行う。
|
|||
|
||||
## module registry
|
||||
|
||||
module registry は、読み込んだ module を loader が返す安定 key で管理する。
|
||||
CLI では canonical path を key とする。
|
||||
組み込み利用では、resource name や static source table の key を使える。
|
||||
module registry は、読み込んだ module を canonical path で管理する。
|
||||
|
||||
```text
|
||||
ModuleRegistry:
|
||||
ModuleKey -> ModuleId
|
||||
CanonicalPath -> ModuleId
|
||||
```
|
||||
|
||||
処理系は、まず root module を parse / desugar して registry に登録する。
|
||||
import 先 module は、この段階で全て読み込む必要はない。
|
||||
|
||||
import expression が評価されたとき、処理系は `SourceLoader` に現在の module key と import specifier を渡す。
|
||||
loader は module key、表示名、source text を返す。
|
||||
module registry は key が未登録なら対象 module を parse / desugar して登録する。
|
||||
import expression が評価されたとき、module registry は path を解決し、未登録なら対象 module を parse / desugar して登録する。
|
||||
登録された module は module root thunk を持つ。
|
||||
同じ module が複数回 import された場合は、同じ `ModuleId` を返す。
|
||||
|
||||
つまり import は module を即時評価しない。
|
||||
module を読み込み、module root を thunk として登録するだけにする。
|
||||
|
||||
AST の `ExprId` は module-local である。
|
||||
そのため runtime が保持する式参照は `ExprRef { module, expr }` として module-qualified にする。
|
||||
|
||||
## demand-driven evaluation
|
||||
|
||||
評価器は、必要になった thunk だけを force する。
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
# Features
|
||||
|
||||
Decodal keeps the embedded core small by making heavy functionality opt-in.
|
||||
|
||||
## Core defaults
|
||||
|
||||
`decodal-core` defaults to `std` only.
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
regex = ["std", "dep:regex"]
|
||||
```
|
||||
|
||||
Building `decodal-core` with `--no-default-features` keeps the core in `no_std + alloc` mode and avoids optional dependencies.
|
||||
|
||||
## Regex
|
||||
|
||||
Regex constraints are implemented behind the `regex` feature.
|
||||
When the feature is disabled, regex constraints parse and compose, but validating a concrete value against them returns an unsupported feature diagnostic.
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
Regex constraints are accumulated during `&` composition.
|
||||
The implementation does not try to prove whether the intersection of two regex constraints is empty.
|
||||
Concrete strings must match every regex constraint attached to the abstract value.
|
||||
|
||||
## CLI features
|
||||
|
||||
`decodal-cli` exposes a matching `regex` feature that enables `decodal-core/regex`.
|
||||
The feature is not enabled by default so the default CLI binary remains small.
|
||||
|
|
@ -15,5 +15,3 @@ bytecode VM や JIT ではなく、AST を demand-driven に評価すること
|
|||
3. [Thunk and Lazy Evaluation](./thunk-and-lazy-evaluation.md)
|
||||
4. [Composition and Materialization](./composition-and-materialization.md)
|
||||
5. [Diagnostics and Fallback](./diagnostics-and-fallback.md)
|
||||
6. [Embedding API](./embedding-api.md)
|
||||
7. [Features](./features.md)
|
||||
|
|
|
|||
|
|
@ -7,16 +7,11 @@ thunk は、まだ評価していない式をあとで評価できるように
|
|||
|
||||
```text
|
||||
Thunk {
|
||||
expr: ExprRef
|
||||
expr: ExprId
|
||||
env: EnvId
|
||||
state: ThunkState
|
||||
}
|
||||
|
||||
ExprRef {
|
||||
module: ModuleId
|
||||
expr: ExprId
|
||||
}
|
||||
|
||||
ThunkState =
|
||||
Unevaluated
|
||||
Evaluating
|
||||
|
|
@ -24,8 +19,7 @@ ThunkState =
|
|||
Error(Diagnostic)
|
||||
```
|
||||
|
||||
`expr` は評価対象の AST node を module-qualified に指す。
|
||||
`ExprId` は module-local な ID なので、runtime では `ModuleId` と組み合わせた `ExprRef` を保持する。
|
||||
`expr` は評価対象の AST node を指す。
|
||||
`env` は、その式を評価するときに使う lexical environment を指す。
|
||||
|
||||
式だけではなく environment も保持するのは、遅延評価された式が定義時の名前解決文脈を必要とするためである。
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
# Development
|
||||
|
||||
This document describes the development workflow for Decodal itself.
|
||||
|
||||
## Rust checks
|
||||
|
||||
Run the normal Rust checks from the repository root.
|
||||
|
||||
```sh
|
||||
cargo fmt --check
|
||||
cargo test
|
||||
cargo check -p decodal-core --no-default-features
|
||||
nix flake check
|
||||
```
|
||||
|
||||
Regex support is optional and should be tested explicitly when touched.
|
||||
|
||||
```sh
|
||||
cargo test -p decodal-core --features regex
|
||||
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
## Tree-sitter grammar
|
||||
|
||||
The Tree-sitter grammar is kept in:
|
||||
|
||||
```text
|
||||
editors/tree-sitter-decodal/
|
||||
```
|
||||
|
||||
Important files:
|
||||
|
||||
```text
|
||||
editors/tree-sitter-decodal/grammar.js
|
||||
editors/tree-sitter-decodal/queries/highlights.scm
|
||||
editors/tree-sitter-decodal/queries/locals.scm
|
||||
editors/tree-sitter-decodal/corpus/basic.txt
|
||||
```
|
||||
|
||||
The grammar is intended to be portable across editors such as Zed, Neovim, Helix, and Emacs.
|
||||
Zed support should consume this grammar rather than relying on a TextMate grammar.
|
||||
|
||||
## Tree-sitter commands
|
||||
|
||||
From the grammar directory:
|
||||
|
||||
```sh
|
||||
cd editors/tree-sitter-decodal
|
||||
npm install
|
||||
npx tree-sitter generate
|
||||
npx tree-sitter test
|
||||
```
|
||||
|
||||
To inspect a parse tree:
|
||||
|
||||
```sh
|
||||
npx tree-sitter parse ../../examples/advanced/main.dcdl
|
||||
```
|
||||
|
||||
The generated parser files under `editors/tree-sitter-decodal/src/` are committed so editor integrations can consume the grammar without regenerating it first.
|
||||
`node_modules/` is ignored and must not be committed.
|
||||
|
||||
## Updating the grammar
|
||||
|
||||
When the Decodal syntax changes:
|
||||
|
||||
1. Update `grammar.js`.
|
||||
2. Run `npx tree-sitter generate`.
|
||||
3. Add or update corpus tests in `corpus/`.
|
||||
4. Update highlight queries in `queries/highlights.scm` if token names changed.
|
||||
5. Run `npx tree-sitter test`.
|
||||
6. Run Rust checks from the repository root if the language parser or examples also changed.
|
||||
|
||||
## Development shell
|
||||
|
||||
The Nix development shell includes Rust tooling, Node.js, and Tree-sitter CLI tooling.
|
||||
|
||||
```sh
|
||||
nix develop
|
||||
```
|
||||
|
||||
The shell provides:
|
||||
|
||||
- `cargo`
|
||||
- `rustc`
|
||||
- `rustfmt`
|
||||
- `clippy`
|
||||
- `node`
|
||||
- `npm`
|
||||
- `tree-sitter`
|
||||
- `nixfmt`
|
||||
|
|
@ -41,7 +41,4 @@ Decodal は Deferred Constraint Data Language、略称 DCDL のプロジェク
|
|||
3. [Thunk and Lazy Evaluation](./design/thunk-and-lazy-evaluation.md)
|
||||
4. [Composition and Materialization](./design/composition-and-materialization.md)
|
||||
5. [Diagnostics and Fallback](./design/diagnostics-and-fallback.md)
|
||||
6. [Embedding API](./design/embedding-api.md)
|
||||
7. [Features](./design/features.md)
|
||||
4. [Development](./development.md)
|
||||
5. [Open Issues](./open-issues.md)
|
||||
4. [Open Issues](./open-issues.md)
|
||||
|
|
|
|||
|
|
@ -31,52 +31,9 @@ A & B = A と B の両方を満たす値または制約
|
|||
|
||||
```dcdl
|
||||
Int & String # エラー
|
||||
> 10 & < 5 # エラー
|
||||
Int & > 10 & < 11 # エラー。整数値の候補が存在しない
|
||||
>= 10 & <= 5 # エラーになりうる
|
||||
```
|
||||
|
||||
## 制約の正規化
|
||||
|
||||
`&` によって abstract value 同士を合成した場合、処理系は軽量に判定できる制約を正規化する。
|
||||
|
||||
正規化対象:
|
||||
|
||||
- primitive type 制約。
|
||||
- 数値比較制約。
|
||||
|
||||
primitive type 制約は、異なる型が同時に要求された場合 conflict になる。
|
||||
|
||||
```dcdl
|
||||
Int & Float
|
||||
Int & String
|
||||
```
|
||||
|
||||
数値比較制約は上下限として正規化される。
|
||||
|
||||
```dcdl
|
||||
Int & >= 1 & <= 65535 & > 443
|
||||
```
|
||||
|
||||
これは概念的に以下へ正規化される。
|
||||
|
||||
```text
|
||||
Type(Int)
|
||||
> 443
|
||||
<= 65535
|
||||
```
|
||||
|
||||
上下限の交差が空であれば conflict になる。
|
||||
`Int` 制約がある場合は、整数候補が存在するかも判定する。
|
||||
|
||||
```dcdl
|
||||
> 10 & < 5 # conflict
|
||||
Int & > 10 & < 11 # conflict
|
||||
Int & >= 10 & <= 10 # OK
|
||||
```
|
||||
|
||||
`Int` の比較制約は整数リテラルを使う。
|
||||
`Float` の比較制約は整数リテラルまたは浮動小数リテラルを使える。
|
||||
|
||||
## 組み込み制約
|
||||
|
||||
最小の組み込み制約は以下である。
|
||||
|
|
@ -96,28 +53,13 @@ IPv4Address
|
|||
|
||||
## 正規表現制約
|
||||
|
||||
正規表現リテラルは文字列制約として使える。
|
||||
正規表現リテラルは文字列制約として使える候補である。
|
||||
|
||||
```dcdl
|
||||
Host = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
```
|
||||
|
||||
正規表現制約は積み重ね可能である。
|
||||
複数の正規表現制約が同じ abstract value に付与された場合、具体文字列はすべての正規表現制約に一致しなければならない。
|
||||
|
||||
```dcdl
|
||||
String & /^a/ & /z$/
|
||||
```
|
||||
|
||||
処理系は、正規表現制約同士の交差が空であるかを合成時に判定する必要はない。
|
||||
つまり、以下は合成時には conflict にならず、具体値検証時に失敗する。
|
||||
|
||||
```dcdl
|
||||
String & /^a$/ & /^b$/
|
||||
```
|
||||
|
||||
正規表現エンジンは optional feature にできる。
|
||||
正規表現 feature が無効な処理系では、正規表現制約の検証は unsupported feature diagnostic になる。
|
||||
ただし、組み込み向け実装では正規表現エンジンを optional feature にできる。
|
||||
軽量実装では代表的な制約を組み込み述語として提供してもよい。
|
||||
|
||||
```dcdl
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@
|
|||
## 構文
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
import ./config.n
|
||||
import "./config.n"
|
||||
```
|
||||
|
||||
import specifier は文字列リテラルとする。
|
||||
パスリテラル構文は採用しない。
|
||||
パス表記の詳細は未確定である。
|
||||
パスリテラルと文字列リテラルの両方を許可するか、どちらかに統一するかは今後決める。
|
||||
|
||||
## モジュール
|
||||
|
||||
|
|
@ -17,29 +18,6 @@ import 先はモジュール単位で読み込まれる。
|
|||
ただし、モジュール全体を即時評価する必要はない。
|
||||
各フィールドは thunk として保持され、必要になったときだけ評価される。
|
||||
|
||||
top-level に field 定義列を書いた module は、recursive module scope を作る。
|
||||
つまり、top-level field は同じ module の他の top-level field から識別子として参照できる。
|
||||
|
||||
```dcdl
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = schema;
|
||||
```
|
||||
|
||||
この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する。
|
||||
通常の object literal 内の field を暗黙に recursive scope にするかは別仕様とする。
|
||||
|
||||
## SourceLoader
|
||||
|
||||
`import` specifier の解決は処理系 core ではなく host 側の `SourceLoader` が行う。
|
||||
CLI では、specifier を現在の module path からの相対 path として解決する。
|
||||
組み込み利用では、resource table や static source map など、filesystem 以外の loader を使える。
|
||||
|
||||
module cache の key は loader が返す安定 key を使う。
|
||||
CLI では canonical path を key とする。
|
||||
|
||||
## 循環 import
|
||||
|
||||
モジュール間に循環参照があっても、必要なフィールドの依存関係が循環していなければ評価できる。
|
||||
|
|
@ -47,25 +25,25 @@ CLI では canonical path を key とする。
|
|||
例:
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
# main.n
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
result = (import ./func.n)(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) =>
|
||||
# func.n
|
||||
(input: (import ./main.n).schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.dcdl` は `main.dcdl` を import しているが、参照しているのは `main.schema` である。
|
||||
`func.n` は `main.n` を import しているが、参照しているのは `main.schema` である。
|
||||
`main.schema` が `main.result` に依存していなければ、この循環 import は成立する。
|
||||
|
||||
## import の評価単位
|
||||
|
|
|
|||
|
|
@ -15,25 +15,6 @@ schema.dcdl
|
|||
service.dcdl
|
||||
```
|
||||
|
||||
## Module source
|
||||
|
||||
ファイル全体は単一の式として書ける。
|
||||
また、top-level に field 定義列を書いた場合は、暗黙の object として扱う。
|
||||
|
||||
```dcdl
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
上の source は以下と同じ意味である。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
}
|
||||
```
|
||||
|
||||
## コメント
|
||||
|
||||
コメントは `#` から行末までとする。
|
||||
|
|
@ -57,7 +38,7 @@ host = "127.0.0.1"; # trailing comment
|
|||
|
||||
## 識別子
|
||||
|
||||
識別子は ASCII 英字で始まり、ASCII 英数字または `_` を続けられる。
|
||||
識別子の厳密な字句規則は未確定である。
|
||||
慣習としては `lower_snake`、`lowerCamel`、`UpperCamel` を使える想定とする。
|
||||
|
||||
```dcdl
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
- 演算子の優先順位。
|
||||
- `rec` の扱い。
|
||||
- コメント構文を `#` のみにするか。
|
||||
- パス import と文字列 import の扱い分け。
|
||||
|
||||
## 型・制約
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{json,toml,yml,gyp}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{c,cc,h}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.{py,pyi}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.swift]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
11
editors/tree-sitter-decodal/.gitattributes
vendored
11
editors/tree-sitter-decodal/.gitattributes
vendored
|
|
@ -1,11 +0,0 @@
|
|||
* text eol=lf
|
||||
|
||||
src/*.json linguist-generated
|
||||
src/parser.c linguist-generated
|
||||
src/tree_sitter/* linguist-generated
|
||||
|
||||
bindings/** linguist-generated
|
||||
binding.gyp linguist-generated
|
||||
setup.py linguist-generated
|
||||
Makefile linguist-generated
|
||||
Package.swift linguist-generated
|
||||
38
editors/tree-sitter-decodal/.gitignore
vendored
38
editors/tree-sitter-decodal/.gitignore
vendored
|
|
@ -1,38 +0,0 @@
|
|||
# Rust artifacts
|
||||
Cargo.lock
|
||||
target/
|
||||
|
||||
# Node artifacts
|
||||
build/
|
||||
prebuilds/
|
||||
node_modules/
|
||||
*.tgz
|
||||
|
||||
# Swift artifacts
|
||||
.build/
|
||||
|
||||
# Go artifacts
|
||||
go.sum
|
||||
_obj/
|
||||
|
||||
# Python artifacts
|
||||
.venv/
|
||||
dist/
|
||||
*.egg-info
|
||||
*.whl
|
||||
|
||||
# C artifacts
|
||||
*.a
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
*.dll
|
||||
*.pc
|
||||
|
||||
# Example dirs
|
||||
/examples/*/
|
||||
|
||||
# Grammar volatiles
|
||||
*.wasm
|
||||
*.obj
|
||||
*.o
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
[package]
|
||||
name = "tree-sitter-decodal"
|
||||
description = "Decodal grammar for tree-sitter"
|
||||
version = "0.0.1"
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "decodal"]
|
||||
categories = ["parsing", "text-editors"]
|
||||
repository = "https://github.com/tree-sitter/tree-sitter-decodal"
|
||||
edition = "2021"
|
||||
autoexamples = false
|
||||
|
||||
build = "bindings/rust/build.rs"
|
||||
include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"]
|
||||
|
||||
[lib]
|
||||
path = "bindings/rust/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
tree-sitter = ">=0.22.6"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.87"
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
VERSION := 0.0.1
|
||||
|
||||
LANGUAGE_NAME := tree-sitter-decodal
|
||||
|
||||
# repository
|
||||
SRC_DIR := src
|
||||
|
||||
PARSER_REPO_URL := $(shell git -C $(SRC_DIR) remote get-url origin 2>/dev/null)
|
||||
|
||||
ifeq ($(PARSER_URL),)
|
||||
PARSER_URL := $(subst .git,,$(PARSER_REPO_URL))
|
||||
ifeq ($(shell echo $(PARSER_URL) | grep '^[a-z][-+.0-9a-z]*://'),)
|
||||
PARSER_URL := $(subst :,/,$(PARSER_URL))
|
||||
PARSER_URL := $(subst git@,https://,$(PARSER_URL))
|
||||
endif
|
||||
endif
|
||||
|
||||
TS ?= tree-sitter
|
||||
|
||||
# ABI versioning
|
||||
SONAME_MAJOR := $(word 1,$(subst ., ,$(VERSION)))
|
||||
SONAME_MINOR := $(word 2,$(subst ., ,$(VERSION)))
|
||||
|
||||
# install directory layout
|
||||
PREFIX ?= /usr/local
|
||||
INCLUDEDIR ?= $(PREFIX)/include
|
||||
LIBDIR ?= $(PREFIX)/lib
|
||||
PCLIBDIR ?= $(LIBDIR)/pkgconfig
|
||||
|
||||
# source/object files
|
||||
PARSER := $(SRC_DIR)/parser.c
|
||||
EXTRAS := $(filter-out $(PARSER),$(wildcard $(SRC_DIR)/*.c))
|
||||
OBJS := $(patsubst %.c,%.o,$(PARSER) $(EXTRAS))
|
||||
|
||||
# flags
|
||||
ARFLAGS ?= rcs
|
||||
override CFLAGS += -I$(SRC_DIR) -std=c11 -fPIC
|
||||
|
||||
# OS-specific bits
|
||||
ifeq ($(OS),Windows_NT)
|
||||
$(error "Windows is not supported")
|
||||
else ifeq ($(shell uname),Darwin)
|
||||
SOEXT = dylib
|
||||
SOEXTVER_MAJOR = $(SONAME_MAJOR).dylib
|
||||
SOEXTVER = $(SONAME_MAJOR).$(SONAME_MINOR).dylib
|
||||
LINKSHARED := $(LINKSHARED)-dynamiclib -Wl,
|
||||
ifneq ($(ADDITIONAL_LIBS),)
|
||||
LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS),
|
||||
endif
|
||||
LINKSHARED := $(LINKSHARED)-install_name,$(LIBDIR)/lib$(LANGUAGE_NAME).$(SONAME_MAJOR).dylib,-rpath,@executable_path/../Frameworks
|
||||
else
|
||||
SOEXT = so
|
||||
SOEXTVER_MAJOR = so.$(SONAME_MAJOR)
|
||||
SOEXTVER = so.$(SONAME_MAJOR).$(SONAME_MINOR)
|
||||
LINKSHARED := $(LINKSHARED)-shared -Wl,
|
||||
ifneq ($(ADDITIONAL_LIBS),)
|
||||
LINKSHARED := $(LINKSHARED)$(ADDITIONAL_LIBS)
|
||||
endif
|
||||
LINKSHARED := $(LINKSHARED)-soname,lib$(LANGUAGE_NAME).so.$(SONAME_MAJOR)
|
||||
endif
|
||||
ifneq ($(filter $(shell uname),FreeBSD NetBSD DragonFly),)
|
||||
PCLIBDIR := $(PREFIX)/libdata/pkgconfig
|
||||
endif
|
||||
|
||||
all: lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT) $(LANGUAGE_NAME).pc
|
||||
|
||||
lib$(LANGUAGE_NAME).a: $(OBJS)
|
||||
$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
lib$(LANGUAGE_NAME).$(SOEXT): $(OBJS)
|
||||
$(CC) $(LDFLAGS) $(LINKSHARED) $^ $(LDLIBS) -o $@
|
||||
ifneq ($(STRIP),)
|
||||
$(STRIP) $@
|
||||
endif
|
||||
|
||||
$(LANGUAGE_NAME).pc: bindings/c/$(LANGUAGE_NAME).pc.in
|
||||
sed -e 's|@URL@|$(PARSER_URL)|' \
|
||||
-e 's|@VERSION@|$(VERSION)|' \
|
||||
-e 's|@LIBDIR@|$(LIBDIR)|' \
|
||||
-e 's|@INCLUDEDIR@|$(INCLUDEDIR)|' \
|
||||
-e 's|@REQUIRES@|$(REQUIRES)|' \
|
||||
-e 's|@ADDITIONAL_LIBS@|$(ADDITIONAL_LIBS)|' \
|
||||
-e 's|=$(PREFIX)|=$${prefix}|' \
|
||||
-e 's|@PREFIX@|$(PREFIX)|' $< > $@
|
||||
|
||||
$(PARSER): $(SRC_DIR)/grammar.json
|
||||
$(TS) generate --no-bindings $^
|
||||
|
||||
install: all
|
||||
install -d '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter '$(DESTDIR)$(PCLIBDIR)' '$(DESTDIR)$(LIBDIR)'
|
||||
install -m644 bindings/c/$(LANGUAGE_NAME).h '$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h
|
||||
install -m644 $(LANGUAGE_NAME).pc '$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
|
||||
install -m644 lib$(LANGUAGE_NAME).a '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a
|
||||
install -m755 lib$(LANGUAGE_NAME).$(SOEXT) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER)
|
||||
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR)
|
||||
ln -sf lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT)
|
||||
|
||||
uninstall:
|
||||
$(RM) '$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).a \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER) \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXTVER_MAJOR) \
|
||||
'$(DESTDIR)$(LIBDIR)'/lib$(LANGUAGE_NAME).$(SOEXT) \
|
||||
'$(DESTDIR)$(INCLUDEDIR)'/tree_sitter/$(LANGUAGE_NAME).h \
|
||||
'$(DESTDIR)$(PCLIBDIR)'/$(LANGUAGE_NAME).pc
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJS) $(LANGUAGE_NAME).pc lib$(LANGUAGE_NAME).a lib$(LANGUAGE_NAME).$(SOEXT)
|
||||
|
||||
test:
|
||||
$(TS) test
|
||||
|
||||
.PHONY: all install uninstall clean test
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
// swift-tools-version:5.3
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "TreeSitterDecodal",
|
||||
products: [
|
||||
.library(name: "TreeSitterDecodal", targets: ["TreeSitterDecodal"]),
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(name: "TreeSitterDecodal",
|
||||
path: ".",
|
||||
exclude: [
|
||||
"Cargo.toml",
|
||||
"Makefile",
|
||||
"binding.gyp",
|
||||
"bindings/c",
|
||||
"bindings/go",
|
||||
"bindings/node",
|
||||
"bindings/python",
|
||||
"bindings/rust",
|
||||
"prebuilds",
|
||||
"grammar.js",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"pyproject.toml",
|
||||
"setup.py",
|
||||
"test",
|
||||
"examples",
|
||||
".editorconfig",
|
||||
".github",
|
||||
".gitignore",
|
||||
".gitattributes",
|
||||
".gitmodules",
|
||||
],
|
||||
sources: [
|
||||
"src/parser.c",
|
||||
// NOTE: if your language has an external scanner, add it here.
|
||||
],
|
||||
resources: [
|
||||
.copy("queries")
|
||||
],
|
||||
publicHeadersPath: "bindings/swift",
|
||||
cSettings: [.headerSearchPath("src")])
|
||||
],
|
||||
cLanguageStandard: .c11
|
||||
)
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "tree_sitter_decodal_binding",
|
||||
"dependencies": [
|
||||
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
||||
],
|
||||
"include_dirs": [
|
||||
"src",
|
||||
],
|
||||
"sources": [
|
||||
"bindings/node/binding.cc",
|
||||
"src/parser.c",
|
||||
# NOTE: if your language has an external scanner, add it here.
|
||||
],
|
||||
"conditions": [
|
||||
["OS!='win'", {
|
||||
"cflags_c": [
|
||||
"-std=c11",
|
||||
],
|
||||
}, { # OS == "win"
|
||||
"cflags_c": [
|
||||
"/std:c11",
|
||||
"/utf-8",
|
||||
],
|
||||
}],
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef TREE_SITTER_DECODAL_H_
|
||||
#define TREE_SITTER_DECODAL_H_
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const TSLanguage *tree_sitter_decodal(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_DECODAL_H_
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
prefix=@PREFIX@
|
||||
libdir=@LIBDIR@
|
||||
includedir=@INCLUDEDIR@
|
||||
|
||||
Name: tree-sitter-decodal
|
||||
Description: Decodal grammar for tree-sitter
|
||||
URL: @URL@
|
||||
Version: @VERSION@
|
||||
Requires: @REQUIRES@
|
||||
Libs: -L${libdir} @ADDITIONAL_LIBS@ -ltree-sitter-decodal
|
||||
Cflags: -I${includedir}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package tree_sitter_decodal
|
||||
|
||||
// #cgo CFLAGS: -std=c11 -fPIC
|
||||
// #include "../../src/parser.c"
|
||||
// // NOTE: if your language has an external scanner, add it here.
|
||||
import "C"
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Get the tree-sitter Language for this grammar.
|
||||
func Language() unsafe.Pointer {
|
||||
return unsafe.Pointer(C.tree_sitter_decodal())
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package tree_sitter_decodal_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
tree_sitter "github.com/smacker/go-tree-sitter"
|
||||
"github.com/tree-sitter/tree-sitter-decodal"
|
||||
)
|
||||
|
||||
func TestCanLoadGrammar(t *testing.T) {
|
||||
language := tree_sitter.NewLanguage(tree_sitter_decodal.Language())
|
||||
if language == nil {
|
||||
t.Errorf("Error loading Decodal grammar")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
module github.com/tree-sitter/tree-sitter-decodal
|
||||
|
||||
go 1.22
|
||||
|
||||
require github.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
#include <napi.h>
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
extern "C" TSLanguage *tree_sitter_decodal();
|
||||
|
||||
// "tree-sitter", "language" hashed with BLAKE2
|
||||
const napi_type_tag LANGUAGE_TYPE_TAG = {
|
||||
0x8AF2E5212AD58ABF, 0xD5006CAD83ABBA16
|
||||
};
|
||||
|
||||
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
||||
exports["name"] = Napi::String::New(env, "decodal");
|
||||
auto language = Napi::External<TSLanguage>::New(env, tree_sitter_decodal());
|
||||
language.TypeTag(&LANGUAGE_TYPE_TAG);
|
||||
exports["language"] = language;
|
||||
return exports;
|
||||
}
|
||||
|
||||
NODE_API_MODULE(tree_sitter_decodal_binding, Init)
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
type BaseNode = {
|
||||
type: string;
|
||||
named: boolean;
|
||||
};
|
||||
|
||||
type ChildNode = {
|
||||
multiple: boolean;
|
||||
required: boolean;
|
||||
types: BaseNode[];
|
||||
};
|
||||
|
||||
type NodeInfo =
|
||||
| (BaseNode & {
|
||||
subtypes: BaseNode[];
|
||||
})
|
||||
| (BaseNode & {
|
||||
fields: { [name: string]: ChildNode };
|
||||
children: ChildNode[];
|
||||
});
|
||||
|
||||
type Language = {
|
||||
name: string;
|
||||
language: unknown;
|
||||
nodeTypeInfo: NodeInfo[];
|
||||
};
|
||||
|
||||
declare const language: Language;
|
||||
export = language;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const root = require("path").join(__dirname, "..", "..");
|
||||
|
||||
module.exports = require("node-gyp-build")(root);
|
||||
|
||||
try {
|
||||
module.exports.nodeTypeInfo = require("../../src/node-types.json");
|
||||
} catch (_) {}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
"Decodal grammar for tree-sitter"
|
||||
|
||||
from ._binding import language
|
||||
|
||||
__all__ = ["language"]
|
||||
|
|
@ -1 +0,0 @@
|
|||
def language() -> int: ...
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
#include <Python.h>
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
TSLanguage *tree_sitter_decodal(void);
|
||||
|
||||
static PyObject* _binding_language(PyObject *self, PyObject *args) {
|
||||
return PyLong_FromVoidPtr(tree_sitter_decodal());
|
||||
}
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
{"language", _binding_language, METH_NOARGS,
|
||||
"Get the tree-sitter language for this grammar."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "_binding",
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = methods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit__binding(void) {
|
||||
return PyModule_Create(&module);
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
fn main() {
|
||||
let src_dir = std::path::Path::new("src");
|
||||
|
||||
let mut c_config = cc::Build::new();
|
||||
c_config.std("c11").include(src_dir);
|
||||
|
||||
#[cfg(target_env = "msvc")]
|
||||
c_config.flag("-utf-8");
|
||||
|
||||
let parser_path = src_dir.join("parser.c");
|
||||
c_config.file(&parser_path);
|
||||
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
|
||||
|
||||
// NOTE: if your language uses an external scanner, uncomment this block:
|
||||
/*
|
||||
let scanner_path = src_dir.join("scanner.c");
|
||||
c_config.file(&scanner_path);
|
||||
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
|
||||
*/
|
||||
|
||||
c_config.compile("tree-sitter-decodal");
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
//! This crate provides Decodal language support for the [tree-sitter][] parsing library.
|
||||
//!
|
||||
//! Typically, you will use the [language][language func] function to add this language to a
|
||||
//! tree-sitter [Parser][], and then use the parser to parse some code:
|
||||
//!
|
||||
//! ```
|
||||
//! let code = r#"
|
||||
//! "#;
|
||||
//! let mut parser = tree_sitter::Parser::new();
|
||||
//! parser.set_language(&tree_sitter_decodal::language()).expect("Error loading Decodal grammar");
|
||||
//! let tree = parser.parse(code, None).unwrap();
|
||||
//! assert!(!tree.root_node().has_error());
|
||||
//! ```
|
||||
//!
|
||||
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
|
||||
//! [language func]: fn.language.html
|
||||
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
|
||||
//! [tree-sitter]: https://tree-sitter.github.io/
|
||||
|
||||
use tree_sitter::Language;
|
||||
|
||||
extern "C" {
|
||||
fn tree_sitter_decodal() -> Language;
|
||||
}
|
||||
|
||||
/// Get the tree-sitter [Language][] for this grammar.
|
||||
///
|
||||
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
|
||||
pub fn language() -> Language {
|
||||
unsafe { tree_sitter_decodal() }
|
||||
}
|
||||
|
||||
/// The content of the [`node-types.json`][] file for this grammar.
|
||||
///
|
||||
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
|
||||
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
||||
|
||||
// Uncomment these to include any queries that this grammar contains
|
||||
|
||||
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
||||
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
|
||||
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
|
||||
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_can_load_grammar() {
|
||||
let mut parser = tree_sitter::Parser::new();
|
||||
parser
|
||||
.set_language(&super::language())
|
||||
.expect("Error loading Decodal grammar");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef TREE_SITTER_DECODAL_H_
|
||||
#define TREE_SITTER_DECODAL_H_
|
||||
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const TSLanguage *tree_sitter_decodal(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_DECODAL_H_
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
==================
|
||||
Top-level fields
|
||||
==================
|
||||
host = String;
|
||||
port = Int & > 443 default 8080;
|
||||
---
|
||||
|
||||
(source_file
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (identifier))
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (default_expression
|
||||
base: (binary_expression
|
||||
left: (identifier)
|
||||
right: (comparison_constraint
|
||||
value: (integer)))
|
||||
fallback: (literal (integer)))))
|
||||
|
||||
==================
|
||||
Import and function
|
||||
==================
|
||||
let
|
||||
schema = import "./schema.dcdl";
|
||||
mk = (cfg: schema.Service) => cfg;
|
||||
in
|
||||
mk({ name = "api"; })
|
||||
---
|
||||
|
||||
(source_file
|
||||
(let_expression
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (import_expression
|
||||
specifier: (string)))
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (function_expression
|
||||
(parameter
|
||||
name: (identifier)
|
||||
constraint: (path_expression
|
||||
object: (identifier)
|
||||
field: (identifier)))
|
||||
body: (identifier)))
|
||||
body: (call_expression
|
||||
function: (identifier)
|
||||
(object
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (literal (string)))))))
|
||||
|
||||
==================
|
||||
Match and patch
|
||||
==================
|
||||
base // {
|
||||
summary = match env {
|
||||
"prod": "production";
|
||||
_: "other";
|
||||
};
|
||||
}
|
||||
---
|
||||
|
||||
(source_file
|
||||
(binary_expression
|
||||
left: (identifier)
|
||||
right: (object
|
||||
(field_definition
|
||||
path: (field_path (identifier))
|
||||
value: (match_expression
|
||||
scrutinee: (identifier)
|
||||
(match_arm
|
||||
pattern: (literal (string))
|
||||
body: (literal (string)))
|
||||
(match_arm
|
||||
body: (literal (string))))))))
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
const PREC = {
|
||||
DEFAULT: 1,
|
||||
PATCH: 2,
|
||||
AND: 3,
|
||||
CALL: 7,
|
||||
PATH: 8,
|
||||
};
|
||||
|
||||
function commaSep(rule) {
|
||||
return optional(seq(rule, repeat(seq(',', rule)), optional(',')));
|
||||
}
|
||||
|
||||
function semiSep(rule) {
|
||||
return optional(seq(rule, repeat(seq(';', rule)), optional(';')));
|
||||
}
|
||||
|
||||
module.exports = grammar({
|
||||
name: 'decodal',
|
||||
|
||||
extras: $ => [
|
||||
/[\s\uFEFF\u2060\u200B]/,
|
||||
$.comment,
|
||||
],
|
||||
|
||||
word: $ => $.identifier,
|
||||
|
||||
conflicts: $ => [
|
||||
[$._expression, $.parameter],
|
||||
],
|
||||
|
||||
rules: {
|
||||
source_file: $ => repeat($._statement),
|
||||
|
||||
_statement: $ => choice(
|
||||
seq($.field_definition, optional(';')),
|
||||
seq($._expression, optional(';')),
|
||||
),
|
||||
|
||||
comment: _ => token(seq('#', /.*/)),
|
||||
|
||||
_expression: $ => choice(
|
||||
$.literal,
|
||||
$.identifier,
|
||||
$.regex_literal,
|
||||
$.comparison_constraint,
|
||||
$.object,
|
||||
$.array,
|
||||
$.let_expression,
|
||||
$.function_expression,
|
||||
$.match_expression,
|
||||
$.import_expression,
|
||||
$.parenthesized_expression,
|
||||
$.call_expression,
|
||||
$.path_expression,
|
||||
$.binary_expression,
|
||||
$.default_expression,
|
||||
),
|
||||
|
||||
literal: $ => choice(
|
||||
$.string,
|
||||
$.integer,
|
||||
$.float,
|
||||
$.boolean,
|
||||
),
|
||||
|
||||
boolean: _ => choice('true', 'false'),
|
||||
|
||||
string: _ => token(seq(
|
||||
'"',
|
||||
repeat(choice(/[^"\\\n]/, /\\./)),
|
||||
'"',
|
||||
)),
|
||||
|
||||
integer: _ => token(/[0-9]+/),
|
||||
float: _ => token(/[0-9]+\.[0-9]+/),
|
||||
|
||||
identifier: _ => /[A-Za-z][A-Za-z0-9_]*/,
|
||||
|
||||
regex_literal: _ => token(seq(
|
||||
'/',
|
||||
repeat1(choice(/[^\/\\\n]/, /\\./)),
|
||||
'/',
|
||||
)),
|
||||
|
||||
object: $ => seq(
|
||||
'{',
|
||||
semiSep($.field_definition),
|
||||
'}',
|
||||
),
|
||||
|
||||
field_definition: $ => seq(
|
||||
field('path', $.field_path),
|
||||
'=',
|
||||
field('value', $._expression),
|
||||
),
|
||||
|
||||
field_path: $ => prec(10, seq(
|
||||
$.identifier,
|
||||
repeat(seq('.', $.identifier)),
|
||||
)),
|
||||
|
||||
array: $ => seq(
|
||||
'[',
|
||||
commaSep($._expression),
|
||||
']',
|
||||
),
|
||||
|
||||
let_expression: $ => seq(
|
||||
'let',
|
||||
repeat(seq($.field_definition, ';')),
|
||||
'in',
|
||||
field('body', $._expression),
|
||||
),
|
||||
|
||||
function_expression: $ => prec.right(seq(
|
||||
'(',
|
||||
commaSep($.parameter),
|
||||
')',
|
||||
'=>',
|
||||
field('body', $._expression),
|
||||
)),
|
||||
|
||||
parameter: $ => seq(
|
||||
field('name', $.identifier),
|
||||
optional(seq(':', field('constraint', $._expression))),
|
||||
),
|
||||
|
||||
match_expression: $ => seq(
|
||||
'match',
|
||||
field('scrutinee', $._expression),
|
||||
'{',
|
||||
semiSep($.match_arm),
|
||||
'}',
|
||||
),
|
||||
|
||||
match_arm: $ => seq(
|
||||
field('pattern', choice('_', $._expression)),
|
||||
':',
|
||||
field('body', $._expression),
|
||||
),
|
||||
|
||||
import_expression: $ => seq(
|
||||
'import',
|
||||
field('specifier', $.string),
|
||||
),
|
||||
|
||||
parenthesized_expression: $ => seq('(', $._expression, ')'),
|
||||
|
||||
call_expression: $ => prec.left(PREC.CALL, seq(
|
||||
field('function', $._expression),
|
||||
'(',
|
||||
commaSep($._expression),
|
||||
')',
|
||||
)),
|
||||
|
||||
path_expression: $ => prec.left(PREC.PATH, seq(
|
||||
field('object', $._expression),
|
||||
'.',
|
||||
field('field', $.identifier),
|
||||
)),
|
||||
|
||||
comparison_constraint: $ => prec(6, seq(
|
||||
field('operator', choice('>', '>=', '<', '<=')),
|
||||
field('value', choice($.integer, $.float)),
|
||||
)),
|
||||
|
||||
binary_expression: $ => choice(
|
||||
prec.left(PREC.AND, seq(
|
||||
field('left', $._expression),
|
||||
field('operator', '&'),
|
||||
field('right', $._expression),
|
||||
)),
|
||||
prec.left(PREC.PATCH, seq(
|
||||
field('left', $._expression),
|
||||
field('operator', token(prec(2, '//'))),
|
||||
field('right', $._expression),
|
||||
)),
|
||||
),
|
||||
|
||||
default_expression: $ => prec.right(PREC.DEFAULT, seq(
|
||||
field('base', $._expression),
|
||||
'default',
|
||||
field('fallback', $._expression),
|
||||
)),
|
||||
},
|
||||
});
|
||||
27
editors/tree-sitter-decodal/package-lock.json
generated
27
editors/tree-sitter-decodal/package-lock.json
generated
|
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"name": "tree-sitter-decodal",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tree-sitter-decodal",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"tree-sitter-cli": "^0.22.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-cli": {
|
||||
"version": "0.22.6",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.22.6.tgz",
|
||||
"integrity": "sha512-s7mYOJXi8sIFkt/nLJSqlYZP96VmKTc3BAwIX0rrrlRxWjWuCwixFqwzxWZBQz4R8Hx01iP7z3cT3ih58BUmZQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tree-sitter": "cli.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
{
|
||||
"name": "tree-sitter-decodal",
|
||||
"version": "0.1.0",
|
||||
"description": "Tree-sitter grammar for Decodal / DCDL",
|
||||
"main": "grammar.js",
|
||||
"types": "bindings/node",
|
||||
"keywords": [
|
||||
"tree-sitter",
|
||||
"decodal",
|
||||
"dcdl"
|
||||
],
|
||||
"files": [
|
||||
"grammar.js",
|
||||
"binding.gyp",
|
||||
"prebuilds/**",
|
||||
"bindings/node/*",
|
||||
"queries/*",
|
||||
"src/**"
|
||||
],
|
||||
"license": "MIT",
|
||||
"tree-sitter": [
|
||||
{
|
||||
"scope": "source.dcdl",
|
||||
"file-types": [
|
||||
"dcdl"
|
||||
]
|
||||
}
|
||||
],
|
||||
"devDependencies": {
|
||||
"tree-sitter-cli": "^0.22.6",
|
||||
"prebuildify": "^6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"generate": "tree-sitter generate",
|
||||
"test": "tree-sitter test",
|
||||
"install": "node-gyp-build",
|
||||
"prebuildify": "prebuildify --napi --strip"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.21.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree_sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tree-sitter-decodal"
|
||||
description = "Decodal grammar for tree-sitter"
|
||||
version = "0.0.1"
|
||||
keywords = ["incremental", "parsing", "tree-sitter", "decodal"]
|
||||
classifiers = [
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Topic :: Software Development :: Compilers",
|
||||
"Topic :: Text Processing :: Linguistic",
|
||||
"Typing :: Typed"
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
license.text = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/tree-sitter/tree-sitter-decodal"
|
||||
|
||||
[project.optional-dependencies]
|
||||
core = ["tree-sitter~=0.21"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build = "cp38-*"
|
||||
build-frontend = "build"
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
(comment) @comment
|
||||
|
||||
(string) @string
|
||||
(regex_literal) @string.regexp
|
||||
(integer) @number
|
||||
(float) @number
|
||||
(boolean) @boolean
|
||||
|
||||
[
|
||||
"let"
|
||||
"in"
|
||||
"match"
|
||||
"import"
|
||||
"default"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"&"
|
||||
"//"
|
||||
"=>"
|
||||
"="
|
||||
">"
|
||||
">="
|
||||
"<"
|
||||
"<="
|
||||
] @operator
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
"("
|
||||
")"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
";"
|
||||
","
|
||||
"."
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
((identifier) @type.builtin
|
||||
(#match? @type.builtin "^(String|Int|Float|Bool)$"))
|
||||
|
||||
(field_definition
|
||||
path: (field_path (identifier) @property))
|
||||
|
||||
(path_expression
|
||||
field: (identifier) @property)
|
||||
|
||||
(parameter
|
||||
name: (identifier) @variable.parameter)
|
||||
|
||||
(function_expression) @function
|
||||
(call_expression
|
||||
function: (identifier) @function.call)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
(parameter name: (identifier) @local.definition)
|
||||
(field_definition path: (field_path (identifier) @local.definition))
|
||||
(identifier) @local.reference
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
from os.path import isdir, join
|
||||
from platform import system
|
||||
|
||||
from setuptools import Extension, find_packages, setup
|
||||
from setuptools.command.build import build
|
||||
from wheel.bdist_wheel import bdist_wheel
|
||||
|
||||
|
||||
class Build(build):
|
||||
def run(self):
|
||||
if isdir("queries"):
|
||||
dest = join(self.build_lib, "tree_sitter_decodal", "queries")
|
||||
self.copy_tree("queries", dest)
|
||||
super().run()
|
||||
|
||||
|
||||
class BdistWheel(bdist_wheel):
|
||||
def get_tag(self):
|
||||
python, abi, platform = super().get_tag()
|
||||
if python.startswith("cp"):
|
||||
python, abi = "cp38", "abi3"
|
||||
return python, abi, platform
|
||||
|
||||
|
||||
setup(
|
||||
packages=find_packages("bindings/python"),
|
||||
package_dir={"": "bindings/python"},
|
||||
package_data={
|
||||
"tree_sitter_decodal": ["*.pyi", "py.typed"],
|
||||
"tree_sitter_decodal.queries": ["*.scm"],
|
||||
},
|
||||
ext_package="tree_sitter_decodal",
|
||||
ext_modules=[
|
||||
Extension(
|
||||
name="_binding",
|
||||
sources=[
|
||||
"bindings/python/tree_sitter_decodal/binding.c",
|
||||
"src/parser.c",
|
||||
# NOTE: if your language uses an external scanner, add it here.
|
||||
],
|
||||
extra_compile_args=[
|
||||
"-std=c11",
|
||||
] if system() != "Windows" else [
|
||||
"/std:c11",
|
||||
"/utf-8",
|
||||
],
|
||||
define_macros=[
|
||||
("Py_LIMITED_API", "0x03080000"),
|
||||
("PY_SSIZE_T_CLEAN", None)
|
||||
],
|
||||
include_dirs=["src"],
|
||||
py_limited_api=True,
|
||||
)
|
||||
],
|
||||
cmdclass={
|
||||
"build": Build,
|
||||
"bdist_wheel": BdistWheel
|
||||
},
|
||||
zip_safe=False
|
||||
)
|
||||
|
|
@ -1,991 +0,0 @@
|
|||
{
|
||||
"name": "decodal",
|
||||
"word": "identifier",
|
||||
"rules": {
|
||||
"source_file": {
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_statement"
|
||||
}
|
||||
},
|
||||
"_statement": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "field_definition"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"comment": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "#"
|
||||
},
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": ".*"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"_expression": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "literal"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "regex_literal"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "comparison_constraint"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "object"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "array"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "let_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "function_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "match_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "import_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "parenthesized_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "call_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "path_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "binary_expression"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "default_expression"
|
||||
}
|
||||
]
|
||||
},
|
||||
"literal": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "string"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "integer"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "float"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "boolean"
|
||||
}
|
||||
]
|
||||
},
|
||||
"boolean": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "false"
|
||||
}
|
||||
]
|
||||
},
|
||||
"string": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "\""
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": "[^\"\\\\\\n]"
|
||||
},
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": "\\\\."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "\""
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"integer": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "PATTERN",
|
||||
"value": "[0-9]+"
|
||||
}
|
||||
},
|
||||
"float": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "PATTERN",
|
||||
"value": "[0-9]+\\.[0-9]+"
|
||||
}
|
||||
},
|
||||
"identifier": {
|
||||
"type": "PATTERN",
|
||||
"value": "[A-Za-z][A-Za-z0-9_]*"
|
||||
},
|
||||
"regex_literal": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "/"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT1",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": "[^\\/\\\\\\n]"
|
||||
},
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": "\\\\."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "/"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"object": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "{"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "field_definition"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "field_definition"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"field_definition": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "path",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "field_path"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "="
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "value",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"field_path": {
|
||||
"type": "PREC",
|
||||
"value": 10,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "."
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"array": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "["
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "]"
|
||||
}
|
||||
]
|
||||
},
|
||||
"let_expression": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "let"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "field_definition"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "in"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "body",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"function_expression": {
|
||||
"type": "PREC_RIGHT",
|
||||
"value": 0,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "("
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "parameter"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "parameter"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ")"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "=>"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "body",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"parameter": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "name",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ":"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "constraint",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"match_expression": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "match"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "scrutinee",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "{"
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "match_arm"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "match_arm"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ";"
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"match_arm": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "pattern",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "_"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ":"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "body",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"import_expression": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "import"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "specifier",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"parenthesized_expression": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "("
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ")"
|
||||
}
|
||||
]
|
||||
},
|
||||
"call_expression": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": 7,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "function",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "("
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
},
|
||||
{
|
||||
"type": "REPEAT",
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ","
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BLANK"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ")"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"path_expression": {
|
||||
"type": "PREC_LEFT",
|
||||
"value": 8,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "object",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "."
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "field",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "identifier"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"comparison_constraint": {
|
||||
"type": "PREC",
|
||||
"value": 6,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "operator",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ">"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": ">="
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "<"
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "<="
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "value",
|
||||
"content": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "integer"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "float"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"binary_expression": {
|
||||
"type": "CHOICE",
|
||||
"members": [
|
||||
{
|
||||
"type": "PREC_LEFT",
|
||||
"value": 3,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "left",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "operator",
|
||||
"content": {
|
||||
"type": "STRING",
|
||||
"value": "&"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "right",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "PREC_LEFT",
|
||||
"value": 2,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "left",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "operator",
|
||||
"content": {
|
||||
"type": "TOKEN",
|
||||
"content": {
|
||||
"type": "PREC",
|
||||
"value": 2,
|
||||
"content": {
|
||||
"type": "STRING",
|
||||
"value": "//"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "right",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_expression": {
|
||||
"type": "PREC_RIGHT",
|
||||
"value": 1,
|
||||
"content": {
|
||||
"type": "SEQ",
|
||||
"members": [
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "base",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "STRING",
|
||||
"value": "default"
|
||||
},
|
||||
{
|
||||
"type": "FIELD",
|
||||
"name": "fallback",
|
||||
"content": {
|
||||
"type": "SYMBOL",
|
||||
"name": "_expression"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"extras": [
|
||||
{
|
||||
"type": "PATTERN",
|
||||
"value": "[\\s\\uFEFF\\u2060\\u200B]"
|
||||
},
|
||||
{
|
||||
"type": "SYMBOL",
|
||||
"name": "comment"
|
||||
}
|
||||
],
|
||||
"conflicts": [
|
||||
[
|
||||
"_expression",
|
||||
"parameter"
|
||||
]
|
||||
],
|
||||
"precedences": [],
|
||||
"externals": [],
|
||||
"inline": [],
|
||||
"supertypes": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,54 +0,0 @@
|
|||
#ifndef TREE_SITTER_ALLOC_H_
|
||||
#define TREE_SITTER_ALLOC_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// Allow clients to override allocation functions
|
||||
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||
|
||||
extern void *(*ts_current_malloc)(size_t);
|
||||
extern void *(*ts_current_calloc)(size_t, size_t);
|
||||
extern void *(*ts_current_realloc)(void *, size_t);
|
||||
extern void (*ts_current_free)(void *);
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc ts_current_malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc ts_current_calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc ts_current_realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free ts_current_free
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef ts_malloc
|
||||
#define ts_malloc malloc
|
||||
#endif
|
||||
#ifndef ts_calloc
|
||||
#define ts_calloc calloc
|
||||
#endif
|
||||
#ifndef ts_realloc
|
||||
#define ts_realloc realloc
|
||||
#endif
|
||||
#ifndef ts_free
|
||||
#define ts_free free
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ALLOC_H_
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
#ifndef TREE_SITTER_ARRAY_H_
|
||||
#define TREE_SITTER_ARRAY_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "./alloc.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
|
||||
#define Array(T) \
|
||||
struct { \
|
||||
T *contents; \
|
||||
uint32_t size; \
|
||||
uint32_t capacity; \
|
||||
}
|
||||
|
||||
/// Initialize an array.
|
||||
#define array_init(self) \
|
||||
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||
|
||||
/// Create an empty array.
|
||||
#define array_new() \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
/// Get a pointer to the element at a given `index` in the array.
|
||||
#define array_get(self, _index) \
|
||||
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||
|
||||
/// Get a pointer to the first element in the array.
|
||||
#define array_front(self) array_get(self, 0)
|
||||
|
||||
/// Get a pointer to the last element in the array.
|
||||
#define array_back(self) array_get(self, (self)->size - 1)
|
||||
|
||||
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_clear(self) ((self)->size = 0)
|
||||
|
||||
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||
/// less than the array's current capacity, this function has no effect.
|
||||
#define array_reserve(self, new_capacity) \
|
||||
_array__reserve((Array *)(self), array_elem_size(self), new_capacity)
|
||||
|
||||
/// Free any memory allocated for this array. Note that this does not free any
|
||||
/// memory allocated for the array's contents.
|
||||
#define array_delete(self) _array__delete((Array *)(self))
|
||||
|
||||
/// Push a new `element` onto the end of the array.
|
||||
#define array_push(self, element) \
|
||||
(_array__grow((Array *)(self), 1, array_elem_size(self)), \
|
||||
(self)->contents[(self)->size++] = (element))
|
||||
|
||||
/// Increase the array's size by `count` elements.
|
||||
/// New elements are zero-initialized.
|
||||
#define array_grow_by(self, count) \
|
||||
do { \
|
||||
if ((count) == 0) break; \
|
||||
_array__grow((Array *)(self), count, array_elem_size(self)); \
|
||||
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||
(self)->size += (count); \
|
||||
} while (0)
|
||||
|
||||
/// Append all elements from one array to the end of another.
|
||||
#define array_push_all(self, other) \
|
||||
array_extend((self), (other)->size, (other)->contents)
|
||||
|
||||
/// Append `count` elements to the end of the array, reading their values from the
|
||||
/// `contents` pointer.
|
||||
#define array_extend(self, count, contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), (self)->size, \
|
||||
0, count, contents \
|
||||
)
|
||||
|
||||
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||
/// the same index, insert `new_count` new elements, reading their values from the
|
||||
/// `new_contents` pointer.
|
||||
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||
_array__splice( \
|
||||
(Array *)(self), array_elem_size(self), _index, \
|
||||
old_count, new_count, new_contents \
|
||||
)
|
||||
|
||||
/// Insert one `element` into the array at the given `index`.
|
||||
#define array_insert(self, _index, element) \
|
||||
_array__splice((Array *)(self), array_elem_size(self), _index, 0, 1, &(element))
|
||||
|
||||
/// Remove one element from the array at the given `index`.
|
||||
#define array_erase(self, _index) \
|
||||
_array__erase((Array *)(self), array_elem_size(self), _index)
|
||||
|
||||
/// Pop the last element off the array, returning the element by value.
|
||||
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||
|
||||
/// Assign the contents of one array to another, reallocating if necessary.
|
||||
#define array_assign(self, other) \
|
||||
_array__assign((Array *)(self), (const Array *)(other), array_elem_size(self))
|
||||
|
||||
/// Swap one array with another
|
||||
#define array_swap(self, other) \
|
||||
_array__swap((Array *)(self), (Array *)(other))
|
||||
|
||||
/// Get the size of the array contents
|
||||
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
///
|
||||
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||
/// out-parameter is set to the existing value's index, and the `exists`
|
||||
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||
/// is set to false.
|
||||
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||
|
||||
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_with`.
|
||||
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||
/// callback to determine the order.
|
||||
#define array_insert_sorted_with(self, compare, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||
/// a given struct field (specified with a leading dot) to determine the order.
|
||||
///
|
||||
/// See also `array_search_sorted_by`.
|
||||
#define array_insert_sorted_by(self, field, value) \
|
||||
do { \
|
||||
unsigned _index, _exists; \
|
||||
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||
if (!_exists) array_insert(self, _index, value); \
|
||||
} while (0)
|
||||
|
||||
// Private
|
||||
|
||||
typedef Array(void) Array;
|
||||
|
||||
/// This is not what you're looking for, see `array_delete`.
|
||||
static inline void _array__delete(Array *self) {
|
||||
if (self->contents) {
|
||||
ts_free(self->contents);
|
||||
self->contents = NULL;
|
||||
self->size = 0;
|
||||
self->capacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_erase`.
|
||||
static inline void _array__erase(Array *self, size_t element_size,
|
||||
uint32_t index) {
|
||||
assert(index < self->size);
|
||||
char *contents = (char *)self->contents;
|
||||
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||
(self->size - index - 1) * element_size);
|
||||
self->size--;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_reserve`.
|
||||
static inline void _array__reserve(Array *self, size_t element_size, uint32_t new_capacity) {
|
||||
if (new_capacity > self->capacity) {
|
||||
if (self->contents) {
|
||||
self->contents = ts_realloc(self->contents, new_capacity * element_size);
|
||||
} else {
|
||||
self->contents = ts_malloc(new_capacity * element_size);
|
||||
}
|
||||
self->capacity = new_capacity;
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_assign`.
|
||||
static inline void _array__assign(Array *self, const Array *other, size_t element_size) {
|
||||
_array__reserve(self, element_size, other->size);
|
||||
self->size = other->size;
|
||||
memcpy(self->contents, other->contents, self->size * element_size);
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_swap`.
|
||||
static inline void _array__swap(Array *self, Array *other) {
|
||||
Array swap = *other;
|
||||
*other = *self;
|
||||
*self = swap;
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||
static inline void _array__grow(Array *self, uint32_t count, size_t element_size) {
|
||||
uint32_t new_size = self->size + count;
|
||||
if (new_size > self->capacity) {
|
||||
uint32_t new_capacity = self->capacity * 2;
|
||||
if (new_capacity < 8) new_capacity = 8;
|
||||
if (new_capacity < new_size) new_capacity = new_size;
|
||||
_array__reserve(self, element_size, new_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
/// This is not what you're looking for, see `array_splice`.
|
||||
static inline void _array__splice(Array *self, size_t element_size,
|
||||
uint32_t index, uint32_t old_count,
|
||||
uint32_t new_count, const void *elements) {
|
||||
uint32_t new_size = self->size + new_count - old_count;
|
||||
uint32_t old_end = index + old_count;
|
||||
uint32_t new_end = index + new_count;
|
||||
assert(old_end <= self->size);
|
||||
|
||||
_array__reserve(self, element_size, new_size);
|
||||
|
||||
char *contents = (char *)self->contents;
|
||||
if (self->size > old_end) {
|
||||
memmove(
|
||||
contents + new_end * element_size,
|
||||
contents + old_end * element_size,
|
||||
(self->size - old_end) * element_size
|
||||
);
|
||||
}
|
||||
if (new_count > 0) {
|
||||
if (elements) {
|
||||
memcpy(
|
||||
(contents + index * element_size),
|
||||
elements,
|
||||
new_count * element_size
|
||||
);
|
||||
} else {
|
||||
memset(
|
||||
(contents + index * element_size),
|
||||
0,
|
||||
new_count * element_size
|
||||
);
|
||||
}
|
||||
}
|
||||
self->size += new_count - old_count;
|
||||
}
|
||||
|
||||
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||
do { \
|
||||
*(_index) = start; \
|
||||
*(_exists) = false; \
|
||||
uint32_t size = (self)->size - *(_index); \
|
||||
if (size == 0) break; \
|
||||
int comparison; \
|
||||
while (size > 1) { \
|
||||
uint32_t half_size = size / 2; \
|
||||
uint32_t mid_index = *(_index) + half_size; \
|
||||
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||
if (comparison <= 0) *(_index) = mid_index; \
|
||||
size -= half_size; \
|
||||
} \
|
||||
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||
if (comparison == 0) *(_exists) = true; \
|
||||
else if (comparison < 0) *(_index) += 1; \
|
||||
} while (0)
|
||||
|
||||
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||
/// parameter by reference in order to work with the generic sorting function above.
|
||||
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default : 4101)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_ARRAY_H_
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
#ifndef TREE_SITTER_PARSER_H_
|
||||
#define TREE_SITTER_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||
#define ts_builtin_sym_end 0
|
||||
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||
|
||||
#ifndef TREE_SITTER_API_H_
|
||||
typedef uint16_t TSStateId;
|
||||
typedef uint16_t TSSymbol;
|
||||
typedef uint16_t TSFieldId;
|
||||
typedef struct TSLanguage TSLanguage;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
TSFieldId field_id;
|
||||
uint8_t child_index;
|
||||
bool inherited;
|
||||
} TSFieldMapEntry;
|
||||
|
||||
typedef struct {
|
||||
uint16_t index;
|
||||
uint16_t length;
|
||||
} TSFieldMapSlice;
|
||||
|
||||
typedef struct {
|
||||
bool visible;
|
||||
bool named;
|
||||
bool supertype;
|
||||
} TSSymbolMetadata;
|
||||
|
||||
typedef struct TSLexer TSLexer;
|
||||
|
||||
struct TSLexer {
|
||||
int32_t lookahead;
|
||||
TSSymbol result_symbol;
|
||||
void (*advance)(TSLexer *, bool);
|
||||
void (*mark_end)(TSLexer *);
|
||||
uint32_t (*get_column)(TSLexer *);
|
||||
bool (*is_at_included_range_start)(const TSLexer *);
|
||||
bool (*eof)(const TSLexer *);
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
TSParseActionTypeShift,
|
||||
TSParseActionTypeReduce,
|
||||
TSParseActionTypeAccept,
|
||||
TSParseActionTypeRecover,
|
||||
} TSParseActionType;
|
||||
|
||||
typedef union {
|
||||
struct {
|
||||
uint8_t type;
|
||||
TSStateId state;
|
||||
bool extra;
|
||||
bool repetition;
|
||||
} shift;
|
||||
struct {
|
||||
uint8_t type;
|
||||
uint8_t child_count;
|
||||
TSSymbol symbol;
|
||||
int16_t dynamic_precedence;
|
||||
uint16_t production_id;
|
||||
} reduce;
|
||||
uint8_t type;
|
||||
} TSParseAction;
|
||||
|
||||
typedef struct {
|
||||
uint16_t lex_state;
|
||||
uint16_t external_lex_state;
|
||||
} TSLexMode;
|
||||
|
||||
typedef union {
|
||||
TSParseAction action;
|
||||
struct {
|
||||
uint8_t count;
|
||||
bool reusable;
|
||||
} entry;
|
||||
} TSParseActionEntry;
|
||||
|
||||
typedef struct {
|
||||
int32_t start;
|
||||
int32_t end;
|
||||
} TSCharacterRange;
|
||||
|
||||
struct TSLanguage {
|
||||
uint32_t version;
|
||||
uint32_t symbol_count;
|
||||
uint32_t alias_count;
|
||||
uint32_t token_count;
|
||||
uint32_t external_token_count;
|
||||
uint32_t state_count;
|
||||
uint32_t large_state_count;
|
||||
uint32_t production_id_count;
|
||||
uint32_t field_count;
|
||||
uint16_t max_alias_sequence_length;
|
||||
const uint16_t *parse_table;
|
||||
const uint16_t *small_parse_table;
|
||||
const uint32_t *small_parse_table_map;
|
||||
const TSParseActionEntry *parse_actions;
|
||||
const char * const *symbol_names;
|
||||
const char * const *field_names;
|
||||
const TSFieldMapSlice *field_map_slices;
|
||||
const TSFieldMapEntry *field_map_entries;
|
||||
const TSSymbolMetadata *symbol_metadata;
|
||||
const TSSymbol *public_symbol_map;
|
||||
const uint16_t *alias_map;
|
||||
const TSSymbol *alias_sequences;
|
||||
const TSLexMode *lex_modes;
|
||||
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||
TSSymbol keyword_capture_token;
|
||||
struct {
|
||||
const bool *states;
|
||||
const TSSymbol *symbol_map;
|
||||
void *(*create)(void);
|
||||
void (*destroy)(void *);
|
||||
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||
unsigned (*serialize)(void *, char *);
|
||||
void (*deserialize)(void *, const char *, unsigned);
|
||||
} external_scanner;
|
||||
const TSStateId *primary_state_ids;
|
||||
};
|
||||
|
||||
static inline bool set_contains(TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||
uint32_t index = 0;
|
||||
uint32_t size = len - index;
|
||||
while (size > 1) {
|
||||
uint32_t half_size = size / 2;
|
||||
uint32_t mid_index = index + half_size;
|
||||
TSCharacterRange *range = &ranges[mid_index];
|
||||
if (lookahead >= range->start && lookahead <= range->end) {
|
||||
return true;
|
||||
} else if (lookahead > range->end) {
|
||||
index = mid_index;
|
||||
}
|
||||
size -= half_size;
|
||||
}
|
||||
TSCharacterRange *range = &ranges[index];
|
||||
return (lookahead >= range->start && lookahead <= range->end);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lexer Macros
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define UNUSED __pragma(warning(suppress : 4101))
|
||||
#else
|
||||
#define UNUSED __attribute__((unused))
|
||||
#endif
|
||||
|
||||
#define START_LEXER() \
|
||||
bool result = false; \
|
||||
bool skip = false; \
|
||||
UNUSED \
|
||||
bool eof = false; \
|
||||
int32_t lookahead; \
|
||||
goto start; \
|
||||
next_state: \
|
||||
lexer->advance(lexer, skip); \
|
||||
start: \
|
||||
skip = false; \
|
||||
lookahead = lexer->lookahead;
|
||||
|
||||
#define ADVANCE(state_value) \
|
||||
{ \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ADVANCE_MAP(...) \
|
||||
{ \
|
||||
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||
if (map[i] == lookahead) { \
|
||||
state = map[i + 1]; \
|
||||
goto next_state; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define SKIP(state_value) \
|
||||
{ \
|
||||
skip = true; \
|
||||
state = state_value; \
|
||||
goto next_state; \
|
||||
}
|
||||
|
||||
#define ACCEPT_TOKEN(symbol_value) \
|
||||
result = true; \
|
||||
lexer->result_symbol = symbol_value; \
|
||||
lexer->mark_end(lexer);
|
||||
|
||||
#define END_STATE() return result;
|
||||
|
||||
/*
|
||||
* Parse Table Macros
|
||||
*/
|
||||
|
||||
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||
|
||||
#define STATE(id) id
|
||||
|
||||
#define ACTIONS(id) id
|
||||
|
||||
#define SHIFT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value) \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_REPEAT(state_value) \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.state = (state_value), \
|
||||
.repetition = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define SHIFT_EXTRA() \
|
||||
{{ \
|
||||
.shift = { \
|
||||
.type = TSParseActionTypeShift, \
|
||||
.extra = true \
|
||||
} \
|
||||
}}
|
||||
|
||||
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||
{{ \
|
||||
.reduce = { \
|
||||
.type = TSParseActionTypeReduce, \
|
||||
.symbol = symbol_name, \
|
||||
.child_count = children, \
|
||||
.dynamic_precedence = precedence, \
|
||||
.production_id = prod_id \
|
||||
}, \
|
||||
}}
|
||||
|
||||
#define RECOVER() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeRecover \
|
||||
}}
|
||||
|
||||
#define ACCEPT_INPUT() \
|
||||
{{ \
|
||||
.type = TSParseActionTypeAccept \
|
||||
}}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // TREE_SITTER_PARSER_H_
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Advanced Decodal example
|
||||
|
||||
This example exercises multiple current Decodal features together:
|
||||
|
||||
- multi-file `import`
|
||||
- top-level recursive module scope
|
||||
- schema/value composition with `&`
|
||||
- deep patch with `//`
|
||||
- `default` fallback during materialization
|
||||
- constrained function parameters
|
||||
- lazy function arguments
|
||||
- `match` expressions
|
||||
- nested dot-path fields
|
||||
- arrays
|
||||
|
||||
Run it with:
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal -- examples/advanced/main.dcdl
|
||||
```
|
||||
|
||||
The entrypoint is `main.dcdl`.
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
let
|
||||
schema = import "./schema.dcdl";
|
||||
profiles = import "./profiles.dcdl";
|
||||
|
||||
mkService = (cfg: schema.Service) =>
|
||||
cfg // {
|
||||
summary = match cfg.env {
|
||||
"prod": "production service";
|
||||
"dev": "development service";
|
||||
_: "custom service";
|
||||
};
|
||||
};
|
||||
|
||||
devService = mkService(
|
||||
profiles.base & {
|
||||
name = "api-dev";
|
||||
port = 8443;
|
||||
}
|
||||
);
|
||||
|
||||
prodService = mkService(
|
||||
profiles.prod & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
);
|
||||
|
||||
disabledService = mkService(
|
||||
profiles.disabled & {
|
||||
name = "api-disabled";
|
||||
port = 10443;
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
services = [
|
||||
devService,
|
||||
prodService,
|
||||
disabledService,
|
||||
];
|
||||
|
||||
selected = match "prod" {
|
||||
"prod": prodService;
|
||||
"dev": devService;
|
||||
_: disabledService;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Environment-specific patches.
|
||||
|
||||
base = {
|
||||
env = "dev";
|
||||
tags = ["decodal", "dev"];
|
||||
};
|
||||
|
||||
prod = base // {
|
||||
env = "prod";
|
||||
host = "api.internal";
|
||||
feature.limit = 100;
|
||||
tags = ["decodal", "prod"];
|
||||
};
|
||||
|
||||
disabled = base // {
|
||||
feature.enable = false;
|
||||
tags = ["decodal", "disabled"];
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# Shared service schema.
|
||||
# Top-level fields are recursive, so SecurePort can refer to Port.
|
||||
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
SecurePort = Port & > 443;
|
||||
|
||||
Service = {
|
||||
name = String;
|
||||
env = String default "dev";
|
||||
host = String default "localhost";
|
||||
port = SecurePort default 8443;
|
||||
|
||||
feature = {
|
||||
enable = Bool default true;
|
||||
limit = Int & >= 0 default 10;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
let
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int & > 443 default 8080;
|
||||
feature.enable = Bool default true;
|
||||
};
|
||||
mkConfig = (cfg: MyConfig) => cfg;
|
||||
in
|
||||
mkConfig({
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
feature.enable = false;
|
||||
})
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
let
|
||||
schema = import "./schema.dcdl";
|
||||
in
|
||||
schema.MyConfig & {
|
||||
host = "localhost";
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
MyConfig = {
|
||||
host = String;
|
||||
port = Int & > 443 default 8080;
|
||||
};
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# Regex example
|
||||
|
||||
Regex constraints are optional. Run this example with the `regex` feature enabled:
|
||||
|
||||
```sh
|
||||
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
Without the feature, regex validation returns an unsupported feature diagnostic.
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
let
|
||||
Identifier = String & /^[A-Za-z_][A-Za-z0-9_]*$/;
|
||||
ApiName = Identifier & /^api_/;
|
||||
in
|
||||
{
|
||||
service = ApiName default "api_gateway";
|
||||
}
|
||||
|
|
@ -35,11 +35,6 @@ rustPlatform.buildRustPackage {
|
|||
|
||||
strictDeps = true;
|
||||
|
||||
cargoBuildFlags = [
|
||||
"-p"
|
||||
"decodal"
|
||||
];
|
||||
|
||||
doInstallCheck = true;
|
||||
installCheckPhase = ''
|
||||
runHook preInstallCheck
|
||||
|
|
|
|||
3
src/main.rs
Normal file
3
src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user