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
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 3 additions & 1 deletion crates/codegen/generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use indexmap::IndexSet;
use infra_utils::cargo::CargoWorkspace;
use ir::builders::{build_ir_models, GenericModel};
use language_definition::model::Language;
use language_v2_definition::model::Language as LanguageV2;
use language_v2_definition::model::{Identifier, Language as LanguageV2};
use semver::Version;
use serde::Serialize;

Expand Down Expand Up @@ -82,6 +82,7 @@ impl RuntimeModelV2 {
pub struct LanguageModelV2 {
name: String,
versions: IndexSet<Version>,
evm_targets: IndexSet<Identifier>,
built_ins: Vec<codegen_v2_semantic::built_ins::BuiltInContextModel>,
}

Expand All @@ -90,6 +91,7 @@ impl LanguageModelV2 {
Self {
name: language.name.to_string(),
versions: language.versions.clone(),
evm_targets: language.evm_targets.clone(),
built_ins: codegen_v2_semantic::built_ins::build_built_ins_model(language),
}
}
Expand Down
6 changes: 3 additions & 3 deletions crates/language-v2/definition/src/compiler/analysis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod p1_definitions;
mod p2_version_specifiers;
mod p2_specifiers;
mod p3_references;
mod p4_unreachable_items;
mod p5_unused_versions;
Expand Down Expand Up @@ -44,8 +44,8 @@ impl Analysis {
for pass in &[
// This pass creates `ItemMetadata` definitions for all language `Item` entries, and verifies they are correct/unique.
p1_definitions::run,
// This pass checks all version ranges in the grammar for correctness.
p2_version_specifiers::run,
// This pass checks all version ranges (`enabled`) and EVM target ranges (`evm_enabled`) in the grammar for correctness.
p2_specifiers::run,
// This pass collects all references between items, making sure they conform to their enabled version ranges.
p3_references::run,
// This pass makes sure all items are reachable from the grammar root.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ use std::fmt::Debug;
use std::rc::Rc;

use indexmap::IndexMap;
use inflector::Inflector;
use semver::Version;

use crate::compiler::analysis::Analysis;
use crate::internals::Spanned;
use crate::model::{
Identifier, SpannedBuiltInContext, SpannedBuiltInDefinition, SpannedBuiltInScope,
SpannedEnumItem, SpannedEnumVariant, SpannedField, SpannedFragmentItem, SpannedItem,
SpannedKeywordItem, SpannedPrecedenceExpression, SpannedPrecedenceItem,
SpannedPrecedenceOperator, SpannedPrimaryExpression, SpannedRepeatedItem, SpannedSeparatedItem,
SpannedStructItem, SpannedTokenItem, SpannedVersionSpecifier,
SpannedEnumItem, SpannedEnumVariant, SpannedEvmTargetSpecifier, SpannedField,
SpannedFragmentItem, SpannedItem, SpannedKeywordItem, SpannedPrecedenceExpression,
SpannedPrecedenceItem, SpannedPrecedenceOperator, SpannedPrimaryExpression,
SpannedRepeatedItem, SpannedSeparatedItem, SpannedStructItem, SpannedTokenItem,
SpannedVersionSpecifier,
};

pub(crate) fn run(analysis: &mut Analysis) {
let language = Rc::clone(&analysis.language);

for evm_target in &language.evm_targets {
check_evm_target_definition(analysis, evm_target);
}

for item in language.items() {
check_item(analysis, item);
}
Expand All @@ -26,6 +32,18 @@ pub(crate) fn run(analysis: &mut Analysis) {
}
}

fn check_evm_target_definition(analysis: &mut Analysis, evm_target: &Spanned<Identifier>) {
let actual = evm_target.as_str();
let expected = actual.to_pascal_case();

if actual != expected.as_str() {
analysis.errors.add(
evm_target,
&EvmTargetErrors::IncorrectCase(evm_target, expected),
);
}
}

fn check_item(analysis: &mut Analysis, item: &SpannedItem) {
match item {
SpannedItem::Struct { item } => {
Expand Down Expand Up @@ -222,10 +240,12 @@ fn check_built_in_definition(analysis: &mut Analysis, definition: &SpannedBuiltI
let SpannedBuiltInDefinition {
name: _,
enabled,
evm_enabled,
internal_parameter: _,
} = definition;

check_version_specifier(analysis, enabled.as_ref());
check_evm_target_specifier(analysis, evm_enabled.as_ref());
}

fn check_version_specifier(
Expand All @@ -240,36 +260,44 @@ fn check_version_specifier(

match &**specifier {
SpannedVersionSpecifier::Always => {
analysis.errors.add(specifier, &Errors::RedundantAlways);
analysis
.errors
.add(specifier, &VersionErrors::RedundantAlways);
}
SpannedVersionSpecifier::Never => {}
SpannedVersionSpecifier::From { from } => {
check_version(analysis, from);

if **from == *first_version {
analysis.errors.add(from, &Errors::RedundantFrom(from));
if *from == first_version {
analysis
.errors
.add(from, &VersionErrors::RedundantFrom(from));
}
}
SpannedVersionSpecifier::Till { till } => {
check_version(analysis, till);

if **till == *first_version {
analysis.errors.add(till, &Errors::RedundantTill(till));
if *till == first_version {
analysis
.errors
.add(till, &VersionErrors::RedundantTill(till));
}
}
SpannedVersionSpecifier::Range { from, till } => {
if from >= till {
analysis
.errors
.add(from, &Errors::UnorderedVersionPair(from, till));
.add(from, &VersionErrors::UnorderedVersionPair(from, till));
return;
}

check_version(analysis, from);
check_version(analysis, till);

if **from == *first_version {
analysis.errors.add(from, &Errors::RedundantRangeFrom(from));
if *from == first_version {
analysis
.errors
.add(from, &VersionErrors::RedundantRangeFrom(from));
}
}
}
Expand All @@ -279,12 +307,79 @@ fn check_version(analysis: &mut Analysis, version: &Spanned<Version>) {
if !analysis.language.versions.contains(version) {
analysis
.errors
.add(version, &Errors::VersionNotFound(version));
.add(version, &VersionErrors::VersionNotFound(version));
}
}

fn check_evm_target_specifier(
analysis: &mut Analysis,
specifier: Option<&Spanned<SpannedEvmTargetSpecifier>>,
) {
let Some(specifier) = specifier else {
return;
};

let first_evm_target = analysis.language.evm_targets.first().unwrap().clone();

match &**specifier {
SpannedEvmTargetSpecifier::Always => {
analysis
.errors
.add(specifier, &EvmTargetErrors::RedundantAlways);
}
SpannedEvmTargetSpecifier::From { from } => {
check_evm_target(analysis, from);

if *from == first_evm_target {
analysis
.errors
.add(from, &EvmTargetErrors::RedundantFrom(from));
}
}
SpannedEvmTargetSpecifier::Till { till } => {
check_evm_target(analysis, till);

if *till == first_evm_target {
analysis
.errors
.add(till, &EvmTargetErrors::RedundantTill(till));
}
}
SpannedEvmTargetSpecifier::Range { from, till } => {
if let (Some(from_index), Some(till_index)) = (
analysis.language.evm_targets.get_index_of(from),
analysis.language.evm_targets.get_index_of(till),
) {
if from_index >= till_index {
analysis
.errors
.add(from, &EvmTargetErrors::UnorderedEvmTargetPair(from, till));
return;
}
}

check_evm_target(analysis, from);
check_evm_target(analysis, till);

if *from == first_evm_target {
analysis
.errors
.add(from, &EvmTargetErrors::RedundantRangeFrom(from));
}
}
}
}

fn check_evm_target(analysis: &mut Analysis, evm_target: &Spanned<Identifier>) {
if !analysis.language.evm_targets.contains(evm_target) {
analysis
.errors
.add(evm_target, &EvmTargetErrors::EvmTargetNotFound(evm_target));
}
}

#[derive(thiserror::Error, Debug)]
enum Errors<'err> {
enum VersionErrors<'err> {
#[error("Version '{0}' does not exist in the language definition.")]
VersionNotFound(&'err Version),
#[error("Version '{0}' must be less than corresponding version '{1}'.")]
Expand All @@ -302,3 +397,25 @@ enum Errors<'err> {
)]
RedundantRangeFrom(&'err Version),
}

#[derive(thiserror::Error, Debug)]
enum EvmTargetErrors<'err> {
#[error("EVM target '{0}' does not exist in the language definition.")]
EvmTargetNotFound(&'err Identifier),
#[error("EVM target '{0}' must use PascalCase spelling ('{1}').")]
IncorrectCase(&'err Identifier, String),
#[error("EVM target '{0}' must precede corresponding EVM target '{1}'.")]
UnorderedEvmTargetPair(&'err Identifier, &'err Identifier),
#[error(
"Explicit 'Always' is redundant, since it is the default when 'evm_enabled' is not specified."
)]
RedundantAlways,
#[error("'From' with the first supported EVM target '{0}' is equivalent to 'Always', and can be removed.")]
RedundantFrom(&'err Identifier),
#[error("'Till' with the first supported EVM target '{0}' produces an empty range, disabling the built-in on every EVM target.")]
RedundantTill(&'err Identifier),
#[error(
"'Range' starting from the first supported EVM target '{0}' can be simplified to 'Till'."
)]
RedundantRangeFrom(&'err Identifier),
}
6 changes: 5 additions & 1 deletion crates/language-v2/definition/src/model/built_ins.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use language_v2_internal_macros::{derive_spanned_type, ParseInputTokens, WriteOutputTokens};
use serde::{Deserialize, Serialize};

use crate::model::{Code, Identifier, VersionSpecifier};
use crate::model::{Code, EvmTargetSpecifier, Identifier, VersionSpecifier};

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[derive_spanned_type(Clone, Debug, ParseInputTokens, WriteOutputTokens)]
Expand All @@ -26,6 +26,10 @@ pub struct BuiltInDefinition {
#[serde(skip_serializing_if = "Option::is_none")]
pub enabled: Option<VersionSpecifier>,

/// The EVM target range this built-in is available.
#[serde(skip_serializing_if = "Option::is_none")]
pub evm_enabled: Option<EvmTargetSpecifier>,

/// A verbatim Rust type to use in the definition of the variant in the
/// internal enum if required for correct resolution or typing.
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
3 changes: 3 additions & 0 deletions crates/language-v2/definition/src/model/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub struct Language {
/// The supported versions of the language
pub versions: IndexSet<Version>,

/// The supported targets of the EVM
pub evm_targets: IndexSet<Identifier>,

/// The lexical contexts of the language, splitting grammar elements based
/// on which lexer can recognize their terminals.
pub contexts: Vec<LexicalContext>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use language_v2_internal_macros::{derive_spanned_type, ParseInputTokens, WriteOutputTokens};
use serde::{Deserialize, Serialize};

use crate::model::Identifier;

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[derive_spanned_type(Clone, Debug, ParseInputTokens, WriteOutputTokens)]
#[serde(tag = "type")]
pub enum EvmTargetSpecifier {
#[default]
Always,
From {
from: Identifier,
},
Till {
till: Identifier,
},
Range {
from: Identifier,
till: Identifier,
},
}
2 changes: 2 additions & 0 deletions crates/language-v2/definition/src/model/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod code;
mod evm_target_specifier;
mod identifier;
mod parser_options;
mod version_specifier;

pub use code::*;
pub use evm_target_specifier::*;
pub use identifier::*;
pub use parser_options::*;
pub use version_specifier::*;
2 changes: 1 addition & 1 deletion crates/language-v2/internal_macros/src/derive/spanned.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ fn get_spanned_type(input: Type) -> Type {

// These are model Types that have a derived 'SpannedXXX' type.
// Let's use that instead, but also wrap it in 'Spanned<T>' because we want to capture its complete span for validation:
"OperatorModel" | "VersionSpecifier" | "ParserOptions" => {
"OperatorModel" | "VersionSpecifier" | "EvmTargetSpecifier" | "ParserOptions" => {
let spanned_type = format_ident!("{}", add_spanned_prefix(type_name));
parse_quote! {
crate::internals::Spanned<crate::model::#spanned_type>
Expand Down
1 change: 1 addition & 0 deletions crates/language-v2/tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ edition.workspace = true
publish = false

[dev-dependencies]
indexmap = { workspace = true }
infra_utils = { workspace = true }
language_v2_definition = { workspace = true }
language_v2_macros = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ language_v2_macros::compile!(Language(
name = Foo,
root_item = Bar,
versions = ["1.0.0"],
evm_targets = [],
contexts = [LexicalContext(
name = Foo,
sections = [Section(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: expected identifier
--> src/fail/p0_parsing/code_string_literal/test.rs:17:83
--> src/fail/p0_parsing/code_string_literal/test.rs:18:83
|
17 | parser_options = ParserOptions(inline = false, verbatim = "raw string not accepted")
18 | parser_options = ParserOptions(inline = false, verbatim = "raw string not accepted")
| ^^^^^^^^^^^^^^^^^^^^^^^^^
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ language_v2_macros::compile!(Language(
name = Foo,
root_item = Bar,
versions = ["1.0.0"],
evm_targets = [],
contexts = [LexicalContext(
name = Foo,
sections = [Section(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: Expected `Code(...)` DSL node.
--> src/fail/p0_parsing/code_wrong_ident/test.rs:17:83
--> src/fail/p0_parsing/code_wrong_ident/test.rs:18:83
|
17 | parser_options = ParserOptions(inline = false, verbatim = NotCode(body))
18 | parser_options = ParserOptions(inline = false, verbatim = NotCode(body))
| ^^^^^^^
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ language_v2_macros::compile!(Language(
name = Foo,
root_item = Bar,
versions = ["1.0.0", "2.0.0", "3.0.0"],
evm_targets = [],
contexts = [LexicalContext(
name = Foo,
sections = [Section(
Expand Down
Loading
Loading