Skip to content

Commit ab95bac

Browse files
authored
Merge pull request #98 from xch-dev/extern-import
Extern function import from hex files
2 parents 22b0566 + b4bdf73 commit ab95bac

34 files changed

Lines changed: 468 additions & 12 deletions

crates/rue-ast/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ impl AstFunctionItem {
232232
self.syntax().children().find_map(AstType::cast)
233233
}
234234

235+
pub fn source_path(&self) -> Option<SyntaxToken> {
236+
self.syntax()
237+
.children_with_tokens()
238+
.filter_map(SyntaxElement::into_token)
239+
.find(|token| token.kind() == SyntaxKind::String)
240+
}
241+
235242
pub fn body(&self) -> Option<AstBlock> {
236243
self.syntax().children().find_map(AstBlock::cast)
237244
}

crates/rue-compiler/src/compile/item/function.rs

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
1+
use std::{fs, path::Path};
2+
3+
use clvmr::{Allocator, serde::node_from_bytes};
14
use indexmap::IndexMap;
25
use log::debug;
36
use rue_ast::{AstFunctionItem, AstNode};
4-
use rue_diagnostic::DiagnosticKind;
5-
use rue_hir::{Declaration, FunctionKind, FunctionSymbol, ParameterSymbol, Symbol, SymbolId, Test};
7+
use rue_diagnostic::{DiagnosticKind, SourceKind};
8+
use rue_hir::{
9+
Declaration, FunctionKind, FunctionSymbol, HirId, ParameterSymbol, Symbol, SymbolId, Test,
10+
};
11+
use rue_parser::SyntaxToken;
612
use rue_types::{FunctionType, Type};
713

814
use crate::{
915
Compiler, CompletionContext, SyntaxItemKind, compile_block, compile_generic_parameters,
10-
compile_type, create_binding,
16+
compile_type, const_eval::decode_node, create_binding,
1117
};
1218

1319
pub fn declare_function(ctx: &mut Compiler, function: &AstFunctionItem) -> SymbolId {
@@ -102,6 +108,12 @@ pub fn declare_function(ctx: &mut Compiler, function: &AstFunctionItem) -> Symbo
102108
ret: return_type,
103109
}));
104110

111+
if function.source_path().is_some() {
112+
for &parameter in parameters.values() {
113+
ctx.reference(Declaration::Symbol(parameter), None);
114+
}
115+
}
116+
105117
let name = function.name().map(|name| ctx.local_name(&name));
106118

107119
*ctx.symbol_mut(symbol) = Symbol::Function(FunctionSymbol {
@@ -115,6 +127,8 @@ pub fn declare_function(ctx: &mut Compiler, function: &AstFunctionItem) -> Symbo
115127
body,
116128
kind: if function.inline().is_some() {
117129
FunctionKind::Inline
130+
} else if function.source_path().is_some() {
131+
FunctionKind::External
118132
} else if function.extern_kw().is_some() {
119133
FunctionKind::Sequential
120134
} else {
@@ -172,7 +186,9 @@ pub fn compile_function(ctx: &mut Compiler, function: &AstFunctionItem, symbol:
172186
ctx.pop_declaration();
173187
}
174188

175-
let resolved_body = if let Some(body) = function.body() {
189+
let resolved_body = if let Some(source_path) = function.source_path() {
190+
compile_external_function(ctx, &source_path)
191+
} else if let Some(body) = function.body() {
176192
let value = compile_block(
177193
ctx,
178194
&body,
@@ -181,10 +197,10 @@ pub fn compile_function(ctx: &mut Compiler, function: &AstFunctionItem, symbol:
181197
function.return_type().is_some(),
182198
);
183199
ctx.assign_type(body.syntax(), value.ty, return_type);
184-
value
200+
value.hir
185201
} else {
186202
debug!("Unresolved function body");
187-
ctx.builtins().unresolved.clone()
203+
ctx.builtins().unresolved.hir
188204
};
189205

190206
ctx.pop_scope(range.end());
@@ -193,7 +209,97 @@ pub fn compile_function(ctx: &mut Compiler, function: &AstFunctionItem, symbol:
193209
unreachable!();
194210
};
195211

196-
*body = resolved_body.hir;
212+
*body = resolved_body;
197213

198214
ctx.pop_declaration();
199215
}
216+
217+
fn compile_external_function(ctx: &mut Compiler, source_path: &SyntaxToken) -> HirId {
218+
let unresolved = ctx.builtins().unresolved.hir;
219+
let path_text = source_path
220+
.text()
221+
.strip_prefix('"')
222+
.and_then(|path| path.strip_suffix('"'))
223+
.unwrap_or(source_path.text());
224+
let relative_path = Path::new(path_text);
225+
226+
if relative_path.is_absolute() {
227+
ctx.diagnostic(source_path, DiagnosticKind::AbsoluteExternalPath);
228+
return unresolved;
229+
}
230+
231+
if relative_path
232+
.extension()
233+
.is_none_or(|extension| extension != "hex")
234+
{
235+
ctx.diagnostic(source_path, DiagnosticKind::InvalidExternalExtension);
236+
return unresolved;
237+
}
238+
239+
let SourceKind::File(source_file) = &ctx.source().kind else {
240+
ctx.diagnostic(source_path, DiagnosticKind::ExternalFromNonFileSource);
241+
return unresolved;
242+
};
243+
let Some(parent) = Path::new(source_file).parent() else {
244+
ctx.diagnostic(source_path, DiagnosticKind::ExternalFromNonFileSource);
245+
return unresolved;
246+
};
247+
let unresolved_path = parent.join(relative_path);
248+
let resolved_path = match unresolved_path.canonicalize() {
249+
Ok(path) => path,
250+
Err(error) => {
251+
ctx.diagnostic(
252+
source_path,
253+
DiagnosticKind::ExternalFileRead(format!("{path_text}: {error}")),
254+
);
255+
return unresolved;
256+
}
257+
};
258+
259+
let (bytes, should_cache) = if let Some(bytes) = ctx.external_program(&resolved_path) {
260+
(bytes.to_vec(), false)
261+
} else {
262+
let contents = match fs::read_to_string(&resolved_path) {
263+
Ok(contents) => contents,
264+
Err(error) => {
265+
ctx.diagnostic(
266+
source_path,
267+
DiagnosticKind::ExternalFileRead(format!("{path_text}: {error}")),
268+
);
269+
return unresolved;
270+
}
271+
};
272+
let hex = contents
273+
.chars()
274+
.filter(|character| !character.is_ascii_whitespace())
275+
.collect::<String>();
276+
let bytes = match hex::decode(hex) {
277+
Ok(bytes) => bytes,
278+
Err(error) => {
279+
ctx.diagnostic(
280+
source_path,
281+
DiagnosticKind::InvalidExternalHex(error.to_string()),
282+
);
283+
return unresolved;
284+
}
285+
};
286+
(bytes, true)
287+
};
288+
289+
let mut allocator = Allocator::new();
290+
let program = match node_from_bytes(&mut allocator, &bytes) {
291+
Ok(program) => program,
292+
Err(error) => {
293+
ctx.diagnostic(
294+
source_path,
295+
DiagnosticKind::InvalidExternalClvm(error.to_string()),
296+
);
297+
return unresolved;
298+
}
299+
};
300+
if should_cache {
301+
ctx.cache_external_program(resolved_path, bytes);
302+
}
303+
let hir = decode_node(ctx, &allocator, program);
304+
ctx.alloc_hir(hir)
305+
}

crates/rue-compiler/src/compiler.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{
33
collections::{HashMap, HashSet},
44
mem,
55
ops::{Deref, DerefMut, Range},
6+
path::{Path, PathBuf},
67
sync::Arc,
78
};
89

@@ -34,6 +35,7 @@ pub struct Compiler {
3435
defaults: HashMap<TypeId, HashMap<String, Value>>,
3536
declaration_stack: Vec<Declaration>,
3637
registered_scopes: HashSet<ScopeId>,
38+
external_programs: HashMap<PathBuf, Vec<u8>>,
3739
}
3840

3941
impl Deref for Compiler {
@@ -68,6 +70,7 @@ impl Compiler {
6870
defaults: HashMap::new(),
6971
declaration_stack: Vec::new(),
7072
registered_scopes: HashSet::new(),
73+
external_programs: HashMap::new(),
7174
};
7275

7376
if options.std {
@@ -112,6 +115,14 @@ impl Compiler {
112115
&self.options
113116
}
114117

118+
pub(crate) fn external_program(&self, path: &Path) -> Option<&[u8]> {
119+
self.external_programs.get(path).map(Vec::as_slice)
120+
}
121+
122+
pub(crate) fn cache_external_program(&mut self, path: PathBuf, program: Vec<u8>) {
123+
self.external_programs.insert(path, program);
124+
}
125+
115126
pub fn set_source(&mut self, source: Source) {
116127
self.source = source;
117128
}

crates/rue-compiler/src/const_eval.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ fn evaluate_const_expr(ctx: &mut Compiler, value: HirId) -> Result<Hir, ConstEva
139139
Ok(decode_node(ctx, &allocator, output.1))
140140
}
141141

142-
fn decode_node(ctx: &mut Compiler, allocator: &Allocator, node: NodePtr) -> Hir {
142+
pub(crate) fn decode_node(ctx: &mut Compiler, allocator: &Allocator, node: NodePtr) -> Hir {
143143
match allocator.sexp(node) {
144144
SExp::Atom => Hir::Bytes(allocator.atom(node).to_vec()),
145145
SExp::Pair(first, rest) => {

crates/rue-diagnostic/src/kind.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ pub enum DiagnosticKind {
2828
#[error("Missing function body")]
2929
MissingFunctionBody,
3030

31+
#[error("External function paths must be relative")]
32+
AbsoluteExternalPath,
33+
34+
#[error("External function path must have a `.hex` extension")]
35+
InvalidExternalExtension,
36+
37+
#[error("External functions cannot be loaded from this source")]
38+
ExternalFromNonFileSource,
39+
40+
#[error("Failed to read external function: {0}")]
41+
ExternalFileRead(String),
42+
43+
#[error("External function contains invalid hex: {0}")]
44+
InvalidExternalHex(String),
45+
46+
#[error("External function contains invalid CLVM: {0}")]
47+
InvalidExternalClvm(String),
48+
3149
#[error("Duplicate symbol `{0}` found in scope")]
3250
DuplicateSymbol(String),
3351

@@ -257,6 +275,12 @@ impl DiagnosticKind {
257275
| Self::UnterminatedBinary
258276
| Self::UnterminatedOctal
259277
| Self::MissingFunctionBody
278+
| Self::AbsoluteExternalPath
279+
| Self::InvalidExternalExtension
280+
| Self::ExternalFromNonFileSource
281+
| Self::ExternalFileRead(..)
282+
| Self::InvalidExternalHex(..)
283+
| Self::InvalidExternalClvm(..)
260284
| Self::DuplicateSymbol(..)
261285
| Self::DuplicateType(..)
262286
| Self::UndeclaredSymbol(..)

crates/rue-hir/src/lower.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ impl<'d, 'a, 'g> Lowerer<'d, 'a, 'g> {
135135

136136
let mut expr = self.lower_hir(&function_env, function.body);
137137

138+
if symbol == self.main && function.kind == FunctionKind::External {
139+
let entire_env = self.arena.alloc(Lir::Path(1));
140+
return self.arena.alloc(Lir::Run(expr, entire_env));
141+
}
142+
138143
if symbol == self.main {
139144
let mut map = HashMap::new();
140145

@@ -171,6 +176,8 @@ impl<'d, 'a, 'g> Lowerer<'d, 'a, 'g> {
171176
expr = self.arena.alloc(Lir::Run(expr, group_env));
172177
}
173178

179+
expr
180+
} else if function.kind == FunctionKind::External {
174181
expr
175182
} else {
176183
self.arena.alloc(Lir::Quote(expr))
@@ -485,7 +492,9 @@ impl<'d, 'a, 'g> Lowerer<'d, 'a, 'g> {
485492
self.lower_symbol_reference(env, symbol)
486493
};
487494

488-
if let Symbol::Function(function) = self.db.symbol(symbol).clone() {
495+
if let Symbol::Function(function) = self.db.symbol(symbol).clone()
496+
&& function.kind != FunctionKind::External
497+
{
489498
let captures: Vec<SymbolId> = self
490499
.graph
491500
.dependencies(symbol, true)
@@ -902,6 +911,10 @@ impl<'d, 'a, 'g> Lowerer<'d, 'a, 'g> {
902911
false
903912
}
904913
Symbol::Function(function) => {
914+
if function.kind == FunctionKind::External {
915+
return false;
916+
}
917+
905918
if self.graph.dependencies(symbol, false).contains(&symbol) {
906919
return false;
907920
}

crates/rue-hir/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pub struct FunctionSymbol {
6565
pub enum FunctionKind {
6666
BinaryTree,
6767
Sequential,
68+
External,
6869
Inline,
6970
}
7071

crates/rue-lexer/src/lexer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ impl<'a> Lexer<'a> {
5555
"import" => TokenKind::Import,
5656
"export" => TokenKind::Export,
5757
"extern" => TokenKind::Extern,
58+
"from" => TokenKind::From,
5859
"inline" => TokenKind::Inline,
5960
"test" => TokenKind::Test,
6061
"mod" => TokenKind::Mod,
@@ -553,6 +554,13 @@ mod tests {
553554
"#]],
554555
);
555556

557+
check(
558+
"from",
559+
expect![[r#"
560+
From
561+
"#]],
562+
);
563+
556564
check(
557565
"inline",
558566
expect![[r#"

crates/rue-lexer/src/token_kind.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub enum TokenKind {
1919
Import,
2020
Export,
2121
Extern,
22+
From,
2223
Inline,
2324
Test,
2425
Mod,

crates/rue-lir/src/codegen.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ fn codegen_impl(
4848
Ok(allocator.new_atom(&atom)?)
4949
}
5050
Lir::Run(callee, env) => {
51+
if options.optimize_static_pairs
52+
&& matches!(arena[*env], Lir::Path(1))
53+
&& is_static_value(arena, *callee)
54+
{
55+
return codegen_static_value(arena, allocator, *callee);
56+
}
57+
5158
let callee = codegen(arena, allocator, *callee)?;
5259
let env = codegen(arena, allocator, *env)?;
5360
Ok(clvm_list!(ClvmOp::Apply, callee, env).to_clvm(allocator)?)
@@ -526,6 +533,28 @@ mod tests {
526533
);
527534
}
528535

536+
#[test]
537+
fn test_run_static_program_with_entire_env() {
538+
let mut arena = Arena::new();
539+
let nil = arena.alloc(Lir::Atom(Vec::new()));
540+
let five = arena.alloc(Lir::Atom(vec![5]));
541+
let args = arena.alloc(Lir::Cons(five, nil));
542+
let two = arena.alloc(Lir::Atom(vec![2]));
543+
let args = arena.alloc(Lir::Cons(two, args));
544+
let add = arena.alloc(Lir::Atom(vec![16]));
545+
let program = arena.alloc(Lir::Cons(add, args));
546+
let env = arena.alloc(Lir::Path(1));
547+
let lir = arena.alloc(Lir::Run(program, env));
548+
549+
check_with_options(
550+
&arena,
551+
lir,
552+
false,
553+
expect!["(a (c (q . 16) (c (q . 2) (c (q . 5) ()))) 1)"],
554+
);
555+
check_with_options(&arena, lir, true, expect!["(+ 2 5)"]);
556+
}
557+
529558
#[test]
530559
fn test_closure() {
531560
let mut arena = Arena::new();

0 commit comments

Comments
 (0)