Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/pine-ast/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use serde::{Deserialize, Serialize};

// Helper function for serde to skip false values
fn is_false(b: &bool) -> bool {
!b
}

/// Function argument - can be positional or named
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Argument {
Expand Down Expand Up @@ -135,15 +140,28 @@ pub enum Stmt {
TypeDecl {
name: String,
fields: Vec<TypeField>,
#[serde(default, skip_serializing_if = "is_false")]
export: bool,
},
MethodDecl {
name: String,
params: Vec<MethodParam>,
body: Vec<Stmt>,
#[serde(default, skip_serializing_if = "is_false")]
export: bool,
},
EnumDecl {
name: String,
fields: Vec<EnumField>,
#[serde(default, skip_serializing_if = "is_false")]
export: bool,
},
FunctionDecl {
name: String,
params: Vec<String>,
body: Vec<Stmt>,
#[serde(default, skip_serializing_if = "is_false")]
export: bool,
},
Export {
item: ExportItem,
Expand Down
71 changes: 66 additions & 5 deletions crates/pine-interpreter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,17 +642,30 @@ impl Interpreter {
Stmt::Break => Err(RuntimeError::BreakOutsideLoop),
Stmt::Continue => Err(RuntimeError::ContinueOutsideLoop),

Stmt::TypeDecl { name, fields } => {
Stmt::TypeDecl {
name,
fields,
export,
} => {
// Create a Type value and store it as a variable
let type_value = Value::Type {
name: name.clone(),
fields: fields.clone(),
};
self.variables.insert(name.clone(), type_value);
self.variables.insert(name.clone(), type_value.clone());

// If exported, also store in exports
if *export {
self.exports.insert(name.clone(), type_value);
}
Ok(None)
}

Stmt::EnumDecl { name, fields } => {
Stmt::EnumDecl {
name,
fields,
export,
} => {
// Create an Object that contains all enum members as fields
let mut enum_fields = HashMap::new();

Expand All @@ -670,7 +683,12 @@ impl Interpreter {
type_name: name.clone(),
fields: Rc::new(RefCell::new(enum_fields)),
};
self.variables.insert(name.clone(), enum_object);
self.variables.insert(name.clone(), enum_object.clone());

// If exported, also store in exports
if *export {
self.exports.insert(name.clone(), enum_object);
}
Ok(None)
}

Expand Down Expand Up @@ -707,6 +725,16 @@ impl Interpreter {
// Get the exports from the library
let library_exports = library_interp.exports();

// Import methods from the library
for (method_name, method_defs) in &library_interp.methods {
for method_def in method_defs {
self.methods
.entry(method_name.clone())
.or_default()
.push(method_def.clone());
}
}

// Create a namespace object containing the exported items
let namespace = Value::Object {
type_name: alias.clone(),
Expand All @@ -729,7 +757,12 @@ impl Interpreter {
Ok(None)
}

Stmt::MethodDecl { name, params, body } => {
Stmt::MethodDecl {
name,
params,
body,
export,
} => {
// Extract the type name from the first parameter's type annotation
let type_name = if let Some(first_param) = params.first() {
first_param.type_annotation.clone().ok_or_else(|| {
Expand All @@ -755,6 +788,34 @@ impl Interpreter {
.or_default()
.push(method_def);

// If exported, store the method in exports
// Methods are exported as part of their type, so we may need to handle this differently
// For now, just mark it as exported (this might need more work)
if *export {
// TODO: Handle method exports properly
}

Ok(None)
}

Stmt::FunctionDecl {
name,
params,
body,
export,
} => {
// Create a function value
let func_value = Value::Function {
params: params.clone(),
body: body.clone(),
};
self.variables.insert(name.clone(), func_value.clone());

// If exported, also store in exports
if *export {
self.exports.insert(name.clone(), func_value);
}

Ok(None)
}
}
Expand Down
65 changes: 51 additions & 14 deletions crates/pine-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,17 +395,17 @@ impl Parser {
fn check_type_annotated_declaration(&mut self) -> Result<Stmt, ParserError> {
// Check for type declaration: type TypeName
if self.match_token(&[TokenType::Type]) {
return self.type_declaration();
return self.type_declaration(false);
}

// Check for enum declaration: enum EnumName
if self.match_token(&[TokenType::Enum]) {
return self.enum_declaration();
return self.enum_declaration(false);
}

// Check for method declaration: method methodName(params) =>
if self.match_token(&[TokenType::Method]) {
return self.method_declaration();
return self.method_declaration(false);
}

// Check for type-annotated declaration without var: int x = ..., float y = ..., int[] x = ...
Expand All @@ -424,7 +424,7 @@ impl Parser {
self.statement()
}

fn type_declaration(&mut self) -> Result<Stmt, ParserError> {
fn type_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
// Parse type name
let type_name = self.expect_identifier()?;

Expand Down Expand Up @@ -471,10 +471,11 @@ impl Parser {
Ok(Stmt::TypeDecl {
name: type_name,
fields,
export,
})
}

fn enum_declaration(&mut self) -> Result<Stmt, ParserError> {
fn enum_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
// Parse enum name
let enum_name = self.expect_identifier()?;

Expand Down Expand Up @@ -515,22 +516,57 @@ impl Parser {
Ok(Stmt::EnumDecl {
name: enum_name,
fields,
export,
})
}

fn export_statement(&mut self) -> Result<Stmt, ParserError> {
// export type typename or export functionname
// export type typename - delegate to type_declaration
if self.match_token(&[TokenType::Type]) {
// export type typename
let type_name = self.expect_identifier()?;
Ok(Stmt::Export {
item: pine_ast::ExportItem::Type(type_name),
return self.type_declaration(true);
}

// export enum enumname - delegate to enum_declaration
if self.match_token(&[TokenType::Enum]) {
return self.enum_declaration(true);
}

// export [method] functionname(params) => body
// Check if it's a method
let is_method = self.match_token(&[TokenType::Method]);

if is_method {
return self.method_declaration(true);
}

// Parse function name
let func_name = self.expect_identifier()?;

// Check if this is a function declaration (followed by '(')
if self.check(&TokenType::LParen) {
// export functionname(params) => body
self.advance(); // consume '('

let params = self.function_params()?;
self.consume(TokenType::RParen, "Expected ')' after function parameters")?;
self.consume(TokenType::Arrow, "Expected '=>'")?;

// Skip optional newline after =>
self.match_token(&[TokenType::Newline]);

// Parse function body (can be a block or single expression)
let body = self.parse_block()?;

Ok(Stmt::FunctionDecl {
name: func_name,
params,
body,
export: true,
})
} else {
// export functionname
let func_name = self.expect_identifier()?;
// Just export functionname (old style - keeping for backward compatibility)
Ok(Stmt::Export {
item: pine_ast::ExportItem::Function(func_name),
item: pine_ast::ExportItem::Type(func_name),
})
}
}
Expand Down Expand Up @@ -585,7 +621,7 @@ impl Parser {
Ok(Stmt::Import { path, alias })
}

fn method_declaration(&mut self) -> Result<Stmt, ParserError> {
fn method_declaration(&mut self, export: bool) -> Result<Stmt, ParserError> {
// Parse method name
let method_name = self.expect_identifier()?;

Expand Down Expand Up @@ -637,6 +673,7 @@ impl Parser {
name: method_name,
params,
body,
export,
})
}

Expand Down
9 changes: 9 additions & 0 deletions crates/pine-parser/testdata/import_export/library.pine
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ library("Point")
export type point
int x
float y

export method set(point this) =>
this.x := 1

export add(x, y) => x + y

export enum Signal
buy = "Buy signal"
sell = "Sell signal"
Loading
Loading