Parser: implement import (#3627)

Based on usage; I believe this handles every case in current `.enso` files.

# Important Notes
- `import` is a built-in macro, so an import statement parses as a `MultiSegmentApp`.
- Every `import` syntax will have a segment whose leading keyword is `import`; however `import` macros can be identified more efficiently by looking at only the first keyword. A `MultiSegmentApp` is an import if and only if its first keyword is in the set { "polyglot", "from", "import" }.
This commit is contained in:
Kaz Wesley 2022-08-02 08:09:20 -07:00 committed by GitHub
parent c6835d2de7
commit 796b1b5b82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 174 additions and 20 deletions

View File

@ -106,7 +106,9 @@ impl FromMeta {
let mut fields = Vec::with_capacity(fields_.size_hint().0);
for field in fields_ {
let meta::Field { name, type_, hide, .. } = field;
let name = name.to_camel_case().expect("Unimplemented: Tuples.");
let mut name_ = meta::FieldName::from_snake_case("field");
name_.append(name.clone());
let name = name_.to_camel_case().expect("Unimplemented: Tuples.");
let field = match self.primitives.get(type_) {
Some(primitive) => Field::primitive(name, *primitive),
None => Field::object(name, self.meta_to_java[type_], true),

View File

@ -16,12 +16,12 @@ use enso_metamodel::java::bincode::MaterializerInput;
// generated fields in Java classes by starting from a `str -> rust::FieldId` query on Rust
// type data, and mapping fields analogously to `rust_to_java` for types.
const CODE_GETTER: &str = "codeRepr";
const TREE_BEGIN: &str = "spanLeftOffsetCodeReprBegin";
const TREE_LEN: &str = "spanLeftOffsetCodeReprLen";
const TOKEN_BEGIN: &str = "codeReprBegin";
const TOKEN_LEN: &str = "codeReprLen";
const TOKEN_OFFSET_BEGIN: &str = "leftOffsetCodeReprBegin";
//const TOKEN_OFFSET_LEN: &str = "leftOffsetCodeReprLen";
const TREE_BEGIN: &str = "fieldSpanLeftOffsetCodeReprBegin";
const TREE_LEN: &str = "fieldSpanLeftOffsetCodeReprLen";
const TOKEN_BEGIN: &str = "fieldCodeReprBegin";
const TOKEN_LEN: &str = "fieldCodeReprLen";
const TOKEN_OFFSET_BEGIN: &str = "fieldLeftOffsetCodeReprBegin";
//const TOKEN_OFFSET_LEN: &str = "fieldLeftOffsetCodeReprLen";
/// Derive deserialization for all types in the typegraph.
pub fn derive(graph: &mut TypeGraph, tree: ClassId, token: ClassId, unsupported: ClassId) {

View File

@ -2,7 +2,6 @@
use crate::macros::pattern::*;
use crate::macros::*;
use crate::syntax::operator;
@ -14,13 +13,62 @@ use crate::syntax::operator;
/// All built-in macro definitions.
pub fn all() -> resolver::SegmentMap<'static> {
let mut macro_map = resolver::SegmentMap::default();
// macro_map.register(if_then());
// macro_map.register(if_then_else());
macro_map.register(if_then());
macro_map.register(if_then_else());
register_import_macros(&mut macro_map);
macro_map.register(group());
macro_map.register(type_def());
macro_map
}
fn register_import_macros(macros: &mut resolver::SegmentMap<'_>) {
use crate::macro_definition;
let defs = [
macro_definition! {("import", everything()) import_body},
macro_definition! {("import", everything(), "as", everything()) import_body},
macro_definition! {("import", everything(), "hiding", everything()) import_body},
macro_definition! {("polyglot", everything(), "import", everything()) import_body},
macro_definition! {
("polyglot", everything(), "import", everything(), "as", everything()) import_body},
macro_definition! {
("polyglot", everything(), "import", everything(), "hiding", everything()) import_body},
macro_definition! {
("from", everything(), "import", everything(), "hiding", everything()) import_body},
macro_definition! {
("from", everything(), "as", everything(), "import", everything()) import_body},
macro_definition! {("from", everything(), "import", everything()) import_body},
];
for def in defs {
macros.register(def);
}
}
fn import_body(segments: NonEmptyVec<MatchedSegment>) -> syntax::Tree {
use operator::resolve_operator_precedence_if_non_empty;
let mut polyglot = None;
let mut from = None;
let mut from_as = None;
let mut import = None;
let mut import_as = None;
let mut hiding = None;
for segment in segments {
let header = segment.header;
let body = resolve_operator_precedence_if_non_empty(segment.result.tokens());
let field = match header.code.as_ref() {
"polyglot" => &mut polyglot,
"from" => &mut from,
"as" if import.is_none() => &mut from_as,
"import" => &mut import,
"as" => &mut import_as,
"hiding" => &mut hiding,
_ => unreachable!(),
};
*field = Some(syntax::tree::MultiSegmentAppSegment { header, body });
}
let import = import.unwrap();
syntax::Tree::import(polyglot, from, from_as, import, import_as, hiding)
}
/// If-then-else macro definition.
pub fn if_then_else<'s>() -> Definition<'s> {
crate::macro_definition! {("if", everything(), "then", everything(), "else", everything())}
@ -33,7 +81,25 @@ pub fn if_then<'s>() -> Definition<'s> {
/// Group macro definition.
pub fn group<'s>() -> Definition<'s> {
crate::macro_definition! {("(", everything(), ")", nothing())}
crate::macro_definition! {("(", everything(), ")", nothing()) group_body}
}
fn group_body(segments: NonEmptyVec<MatchedSegment>) -> syntax::Tree {
use operator::resolve_operator_precedence_if_non_empty;
use syntax::token;
macro_rules! into_symbol {
($token:expr) => {{
let token::Token { left_offset, code, .. } = $token;
token::symbol(left_offset, code)
}};
}
let (close, mut segments) = segments.pop();
let close = into_symbol!(close.header);
let segment = segments.pop().unwrap();
let open = into_symbol!(segment.header);
let body = segment.result.tokens();
let body = resolve_operator_precedence_if_non_empty(body);
syntax::Tree::group(open, body, close)
}
/// New type definition macro definition.

View File

@ -71,6 +71,7 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
/// [`Tree`] variants definition. See its docs to learn more.
#[tagged_enum]
#[derive(Clone, Eq, PartialEq, Visitor, Serialize, Reflect, Deserialize)]
#[allow(clippy::large_enum_variant)] // Inefficient. Will be fixed in #182878443.
#[tagged_enum(apply_attributes_to = "variants")]
#[reflect(inline)]
pub enum Variant<'s> {
@ -193,9 +194,25 @@ macro_rules! with_ast_definition { ($f:ident ($($args:tt)*)) => { $f! { $($args)
/// It is an error for this to be empty.
pub body: Option<Tree<'s>>,
},
/// An import statement.
Import {
pub polyglot: Option<MultiSegmentAppSegment<'s>>,
pub from: Option<MultiSegmentAppSegment<'s>>,
pub from_as: Option<MultiSegmentAppSegment<'s>>,
pub import: MultiSegmentAppSegment<'s>,
pub import_as: Option<MultiSegmentAppSegment<'s>>,
pub hiding: Option<MultiSegmentAppSegment<'s>>,
},
/// An expression grouped by matched parentheses.
Group {
pub open: token::Symbol<'s>,
pub body: Option<Tree<'s>>,
pub close: token::Symbol<'s>,
}
}
}};}
macro_rules! generate_variant_constructors {
(
$(#$enum_meta:tt)*

View File

@ -49,9 +49,7 @@ fn application() {
#[test]
fn parentheses_simple() {
let expected = block![
(MultiSegmentApp #(((Symbol "(") (App (Ident a) (Ident b))) ((Symbol ")") ())))
];
let expected = block![(Group "(" (App (Ident a) (Ident b)) ")")];
test("(a b)", expected);
}
@ -67,12 +65,11 @@ fn section_simple() {
fn parentheses_nested() {
#[rustfmt::skip]
let expected = block![
(MultiSegmentApp #(
((Symbol "(")
(App (MultiSegmentApp #(((Symbol "(") (App (Ident a) (Ident b))) ((Symbol ")") ())))
(Ident c)))
((Symbol ")") ())))
];
(Group
"("
(App (Group "(" (App (Ident a) (Ident b)) ")")
(Ident c))
")")];
test("((a b) c)", expected);
}
@ -151,6 +148,23 @@ fn type_def_full() {
test(&code.join("\n"), expected);
}
#[test]
fn type_def_nested() {
#[rustfmt::skip]
let code = [
"type Foo",
" type Bar",
" type Baz",
];
#[rustfmt::skip]
let expected = block![
(TypeDef (Ident type) (Ident Foo) #() #()
#((TypeDef (Ident type) (Ident Bar) #() #() #())
(TypeDef (Ident type) (Ident Baz) #() #() #())))
];
test(&code.join("\n"), expected);
}
// === Variable Assignment ===
@ -362,6 +376,61 @@ fn plus_negative() {
}
// === Import ===
#[test]
fn import() {
#[rustfmt::skip]
let cases = [
("import project.IO", block![
(Import () () () ((Ident import) (OprApp (Ident project) (Ok ".") (Ident IO))) () ())]),
("import Standard.Base as Enso_List", block![
(Import () () ()
((Ident import) (OprApp (Ident Standard) (Ok ".") (Ident Base)))
((Ident as) (Ident Enso_List))
())]),
("from Standard.Base import all", block![
(Import ()
((Ident from) (OprApp (Ident Standard) (Ok ".") (Ident Base)))
()
((Ident import) (Ident all))
() ())]),
("from Standard.Base import all hiding Number, Boolean", block![
(Import ()
((Ident from) (OprApp (Ident Standard) (Ok ".") (Ident Base)))
()
((Ident import) (Ident all))
()
((Ident hiding)
(App (OprSectionBoundary (OprApp (Ident Number) (Ok ",") ())) (Ident Boolean))))]),
("from Standard.Table as Column_Module import Column", block![
(Import ()
((Ident from) (OprApp (Ident Standard) (Ok ".") (Ident Table)))
((Ident as) (Ident Column_Module))
((Ident import) (Ident Column))
() ())]),
("polyglot java import java.lang.Float", block![
(Import
((Ident polyglot) (Ident java))
()
()
((Ident import)
(OprApp (OprApp (Ident java) (Ok ".") (Ident lang)) (Ok ".") (Ident Float)))
() ())]),
("polyglot java import java.net.URI as Java_URI", block![
(Import
((Ident polyglot) (Ident java))
()
()
((Ident import)
(OprApp (OprApp (Ident java) (Ok ".") (Ident net)) (Ok ".") (Ident URI)))
((Ident as) (Ident Java_URI))
())]),
];
cases.into_iter().for_each(|(code, expected)| test(code, expected));
}
// ====================
// === Test Support ===