Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compiler scopes and intermediate object construction rework #132

Merged
merged 8 commits into from
Jan 18, 2024
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
443 changes: 226 additions & 217 deletions src/compiler/ast.rs

Large diffs are not rendered by default.

620 changes: 97 additions & 523 deletions src/compiler/compiler.rs

Large diffs are not rendered by default.

23 changes: 11 additions & 12 deletions src/compiler/iml/imlop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use super::*;
use crate::reader::Offset;
use crate::utils;
use crate::Compiler;
use crate::{Object, RefValue};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -73,35 +72,35 @@ impl ImlOp {
}

/// Load value; This is only a shortcut for creating an ImlOp::Load{}
pub fn load(_compiler: &mut Compiler, offset: Option<Offset>, target: ImlValue) -> ImlOp {
pub fn load(_scope: &Scope, offset: Option<Offset>, target: ImlValue) -> ImlOp {
ImlOp::Load { offset, target }
}

/// Load unknown value by name
pub fn load_by_name(compiler: &mut Compiler, offset: Option<Offset>, name: String) -> ImlOp {
let value = ImlValue::Name { offset, name }.try_resolve(compiler);
pub fn load_by_name(scope: &Scope, offset: Option<Offset>, name: String) -> ImlOp {
let value = ImlValue::Name { offset, name }.try_resolve(scope);

Self::load(compiler, offset.clone(), value)
Self::load(scope, offset.clone(), value)
}

/// Call known value
pub fn call(
compiler: &mut Compiler,
scope: &Scope,
offset: Option<Offset>,
target: ImlValue,
args: Option<(usize, bool)>,
) -> ImlOp {
let target = target.try_resolve(compiler);
let target = target.try_resolve(scope);

// When args is unset, and the value is not callable without arguments,
// consider this call is a load.
if args.is_none() && !target.is_callable(true) {
// Currently not planned as final
return Self::load(compiler, offset, target);
return Self::load(scope, offset, target);
}

if target.is_consuming() {
compiler.parselet_mark_consuming();
scope.parselet().borrow().model.borrow_mut().is_consuming = true;
}

ImlOp::Call {
Expand All @@ -113,19 +112,19 @@ impl ImlOp {

/// Call unknown value by name
pub fn call_by_name(
compiler: &mut Compiler,
scope: &Scope,
offset: Option<Offset>,
name: String,
args: Option<(usize, bool)>,
) -> ImlOp {
// Perform early consumable detection depending on identifier's name
if utils::identifier_is_consumable(&name) {
compiler.parselet_mark_consuming();
scope.parselet().borrow().model.borrow_mut().is_consuming = true;
}

ImlOp::Call {
offset: offset.clone(),
target: ImlValue::Name { offset, name }.try_resolve(compiler),
target: ImlValue::Name { offset, name }.try_resolve(scope),
args,
}
}
Expand Down
76 changes: 63 additions & 13 deletions src/compiler/iml/imlparselet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ use std::rc::Rc;

/** Intermediate parselet model.

The model defines the code and local varibles of the parselet, and is shared by
several parselet configurations. */
#[derive(Debug)]
The model defines the signature, local variables and code of a function or
parselet. It is shared by several parselet instances.

The struct was designed to be used for parselet construction during compilation,
therefore it provides an interface to define named and temporary variables
during compilation.
*/
#[derive(Debug, Clone)]
pub(in crate::compiler) struct ImlParseletModel {
pub is_consuming: bool, // Flag if parselet is consuming
pub locals: usize, // Total number of local variables present (including arguments)
pub signature: IndexMap<String, ImlValue>, // Arguments signature with default values
pub variables: IndexMap<String, usize>, // Named local variables
pub temporaries: Vec<usize>, // Unnamed temporary variables
pub begin: ImlOp, // Begin intermediate operations
pub end: ImlOp, // End intermediate operations
pub body: ImlOp, // Body intermediate Operations
Expand All @@ -26,30 +33,73 @@ pub(in crate::compiler) struct ImlParseletModel {
impl ImlParseletModel {
pub fn new(signature: Option<IndexMap<String, ImlValue>>) -> Self {
let signature = signature.unwrap_or(IndexMap::new());
// Generate variables from signature, addresses are enumerated!
let variables = signature
.keys()
.enumerate()
.map(|(index, key)| (key.to_string(), index))
.collect();

Self {
is_consuming: false,
locals: signature.len(),
signature,
variables,
temporaries: Vec::new(),
begin: ImlOp::Nop,
end: ImlOp::Nop,
body: ImlOp::Nop,
}
}

// Return unique memory address of this model
pub fn id(&self) -> usize {
self as *const ImlParseletModel as usize
}

/// Allocate new variable
fn allocate(&mut self) -> usize {
let addr = self.locals;
self.locals += 1;
addr
}

/// Declare new or return address of named variables
pub fn get_named(&mut self, name: &str) -> usize {
match self.variables.get(name) {
Some(addr) => *addr,
None => {
let addr = self.allocate();
self.variables.insert(name.to_string(), addr);
addr
}
}
}

/// Claim temporary (unnamed) variable.
/// The variable is either being reused or freshly allocated.
/// After use of the temporary address, return_temporary should be called.
pub fn claim_temp(&mut self) -> usize {
match self.temporaries.pop() {
Some(addr) => addr,
None => self.allocate(),
}
}

// Returns a temporary variable address for (eventual) reuse later.
pub fn return_temp(&mut self, addr: usize) {
self.temporaries.push(addr)
}
}

// ImlParseletInstance
// ----------------------------------------------------------------------------

/** Intermediate parselet configuration.
/** Intermediate parselet instance.

A parselet configuration is a model with as given constants definition.
The constants definition might be generic, which needs to be resolved first
before a parselet configuration is turned into a parselet.
A parselet instance is a model with as given generics definition.
The generics definition needs to be resolved first, before a parselet instance
is turned into a executable parselet.
*/
#[allow(dead_code)]
#[derive(Debug)]
Expand Down Expand Up @@ -135,12 +185,6 @@ impl std::cmp::PartialOrd for ImlParseletInstance {
}
}

impl From<ImlParseletInstance> for ImlValue {
fn from(parselet: ImlParseletInstance) -> Self {
ImlValue::Parselet(ImlParselet::new(parselet))
}
}

// ImlParselet
// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -292,3 +336,9 @@ impl std::ops::DerefMut for ImlParselet {
&mut self.instance
}
}

impl From<ImlParselet> for ImlValue {
fn from(parselet: ImlParselet) -> Self {
ImlValue::Parselet(parselet)
}
}
55 changes: 27 additions & 28 deletions src/compiler/iml/imlvalue.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Intermediate value representation
use super::*;
use crate::compiler::Compiler;
use crate::reader::Offset;
use crate::utils;
use crate::value::{Object, RefValue, Value};
Expand Down Expand Up @@ -77,13 +76,13 @@ impl ImlValue {
}

/// Try to resolve immediatelly, otherwise push shared reference to compiler's unresolved ImlValue.
pub fn try_resolve(mut self, compiler: &mut Compiler) -> Self {
if self.resolve(compiler) {
pub fn try_resolve(mut self, scope: &Scope) -> Self {
if self.resolve(scope) {
return self;
}

let shared = Self::Unresolved(Rc::new(RefCell::new(self)));
compiler.usages.push(shared.clone());
scope.usages.borrow_mut().push(shared.clone());
shared
}

Expand All @@ -95,19 +94,19 @@ impl ImlValue {

Returns true in case the provided value is (already) resolved.
*/
pub fn resolve(&mut self, compiler: &mut Compiler) -> bool {
pub fn resolve(&mut self, scope: &Scope) -> bool {
let resolve = match self {
Self::Unresolved(value) => match value.try_borrow_mut() {
Ok(mut value) => {
if value.resolve(compiler) {
if value.resolve(scope) {
Some(value.clone())
} else {
None
}
}
Err(_) => todo!("Recursive resolve() impossible by design, see bug #127"),
},
Self::Name { offset, name, .. } => compiler.get(offset.clone(), &name),
Self::Name { offset, name, .. } => scope.resolve_name(offset.clone(), &name),
Self::Instance {
offset,
target,
Expand All @@ -116,18 +115,18 @@ impl ImlValue {
severity,
is_generated,
} => {
let mut is_resolved = target.resolve(compiler);
let mut is_resolved = target.resolve(scope);

// Resolve sequential generic args
for arg in args.iter_mut() {
if !arg.1.resolve(compiler) {
if !arg.1.resolve(scope) {
is_resolved = false;
}
}

// Resolve named generic args
for narg in nargs.values_mut() {
if !narg.1.resolve(compiler) {
if !narg.1.resolve(scope) {
is_resolved = false;
}
}
Expand Down Expand Up @@ -155,86 +154,86 @@ impl ImlValue {

// Check integrity of constant names
if let Self::Unset = arg.1 {
compiler.errors.push(Error::new(
scope.error(
arg.0,
format!("Expecting argument for generic '{}'", name),
));
);
} else if arg.1.is_consuming() {
if !utils::identifier_is_consumable(name) {
compiler.errors.push(Error::new(
scope.error(
arg.0,
format!(
"Cannot assign consumable {} to non-consumable generic '{}'",
arg.1, name
)
));
);
}
} else if utils::identifier_is_consumable(name) {
compiler.errors.push(Error::new(
scope.error(
arg.0,
format!(
"Cannot assign non-consumable {} to consumable generic {} of {}",
arg.1, name, parselet
)
));
);
}

generics.insert(name.clone(), arg.1);
}

// Report any errors for unconsumed generic arguments.
if !args.is_empty() {
compiler.errors.push(Error::new(
scope.error(
args[0].0, // report first parameter
format!(
"{} got too many generic arguments ({} in total, expected {})",
target,
generics.len() + args.len(),
generics.len()
),
));
);
}

for (name, (offset, _)) in nargs {
if generics.get(name).is_some() {
compiler.errors.push(Error::new(
scope.error(
*offset,
format!(
"{} already got generic argument '{}'",
target, name
),
));
);
} else {
compiler.errors.push(Error::new(
scope.error(
*offset,
format!(
"{} does not accept generic argument named '{}'",
target, name
),
));
);
}
}

// Make a parselet instance from the instance definition;
// This can be the final parselet instance, but constants
// might contain generic references as well, which are being
// resolved during further compilation and derivation.
let instance = ImlValue::from(ImlParseletInstance {
let parselet = ImlValue::from(ImlParselet::new(ImlParseletInstance {
model: parselet.model.clone(),
generics,
offset: parselet.offset.clone(),
name: parselet.name.clone(),
severity: severity.unwrap_or(parselet.severity),
is_generated: *is_generated,
});
}));

Some(instance)
Some(parselet)
}
target => {
compiler.errors.push(Error::new(
scope.error(
*offset,
format!("Cannot create instance from '{}'", target),
));
);
return false;
}
}
Expand Down Expand Up @@ -410,7 +409,7 @@ impl std::fmt::Display for ImlValue {
Self::Value(value) => write!(f, "{}", value.repr()),
Self::Parselet(parselet) => write!(
f,
"{:?}",
"{}",
parselet
.borrow()
.name
Expand Down
1 change: 1 addition & 0 deletions src/compiler/iml/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//! Tokay intermediate code representation
pub use super::*;
pub use crate::vm::*;

mod imlop;
Expand Down
Loading
Loading