mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 13:15:52 +03:00
Generate Scala Classes From The Stub AST (#1044)
This commit is contained in:
parent
cd6858a01e
commit
8bb583df5e
@ -15,12 +15,12 @@ structured and how it should behave.
|
||||
|
||||
- [Universal Launcher](#universal-launcher)
|
||||
- [Enso Distribution Layout](#enso-distribution-layout)
|
||||
- [Portable Enso Distribution Layout](#portable-enso-distribution-layout)
|
||||
- [Installed Enso Distribution Layout](#installed-enso-distribution-layout)
|
||||
- [Installing from a Portable Distribution](#installing-from-a-portable-distribution)
|
||||
- [Portable Enso Distribution Layout](#portable-enso-distribution-layout)
|
||||
- [Installed Enso Distribution Layout](#installed-enso-distribution-layout)
|
||||
- [Installing from a Portable Distribution](#installing-from-a-portable-distribution)
|
||||
- [Layout of an Enso Version Package](#layout-of-an-enso-version-package)
|
||||
- [Standard Library](#standard-library)
|
||||
- [Resolvers](#resolvers)
|
||||
- [Standard Library](#standard-library)
|
||||
- [Resolvers](#resolvers)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
|
@ -19,23 +19,23 @@ This document describes available command-line options of the Enso launcher.
|
||||
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||
|
||||
- [Commands](#commands)
|
||||
- [`new`](#new)
|
||||
- [`install engine`](#install-engine)
|
||||
- [`uninstall engine`](#uninstall-engine)
|
||||
- [`install distribution`](#install-distribution)
|
||||
- [`uninstall distribution`](#uninstall-distribution)
|
||||
- [`list`](#list)
|
||||
- [`default`](#default)
|
||||
- [`config`](#config)
|
||||
- [`run`](#run)
|
||||
- [`repl`](#repl)
|
||||
- [`language-server`](#language-server)
|
||||
- [`upgrade`](#upgrade)
|
||||
- [`version`](#version)
|
||||
- [`help`](#help)
|
||||
- [`new`](#new)
|
||||
- [`install engine`](#install-engine)
|
||||
- [`uninstall engine`](#uninstall-engine)
|
||||
- [`install distribution`](#install-distribution)
|
||||
- [`uninstall distribution`](#uninstall-distribution)
|
||||
- [`list`](#list)
|
||||
- [`default`](#default)
|
||||
- [`config`](#config)
|
||||
- [`run`](#run)
|
||||
- [`repl`](#repl)
|
||||
- [`language-server`](#language-server)
|
||||
- [`upgrade`](#upgrade)
|
||||
- [`version`](#version)
|
||||
- [`help`](#help)
|
||||
- [General Options](#general-options)
|
||||
- [`--version`](#--version)
|
||||
- [`--use-system-jvm`](#--use-system-jvm)
|
||||
- [`--version`](#--version)
|
||||
- [`--use-system-jvm`](#--use-system-jvm)
|
||||
- [JVM Options](#jvm-options)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
@ -15,24 +15,24 @@ command-line interface is described in the [CLI](./launcher-cli.md) document.
|
||||
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||
|
||||
- [Launcher Distribution](#launcher-distribution)
|
||||
- [Using Multiple Launcher Versions Side-By-Side](#using-multiple-launcher-versions-side-by-side)
|
||||
- [Detecting Portable Distribution](#detecting-portable-distribution)
|
||||
- [Using Multiple Launcher Versions Side-By-Side](#using-multiple-launcher-versions-side-by-side)
|
||||
- [Detecting Portable Distribution](#detecting-portable-distribution)
|
||||
- [Launcher Build](#launcher-build)
|
||||
- [Portability](#portability)
|
||||
- [Portability](#portability)
|
||||
- [Project Management](#project-management)
|
||||
- [Creating a Project](#creating-a-project)
|
||||
- [Per-Project Enso Version](#per-project-enso-version)
|
||||
- [Project Configuration](#project-configuration)
|
||||
- [Creating a Project](#creating-a-project)
|
||||
- [Per-Project Enso Version](#per-project-enso-version)
|
||||
- [Project Configuration](#project-configuration)
|
||||
- [Enso and Graal Version Management](#enso-and-graal-version-management)
|
||||
- [GraalVM Override](#graalvm-override)
|
||||
- [Downloading Enso Releases](#downloading-enso-releases)
|
||||
- [Downloading GraalVM Releases](#downloading-graalvm-releases)
|
||||
- [GraalVM Override](#graalvm-override)
|
||||
- [Downloading Enso Releases](#downloading-enso-releases)
|
||||
- [Downloading GraalVM Releases](#downloading-graalvm-releases)
|
||||
- [Running Enso Components](#running-enso-components)
|
||||
- [Running Plugins](#running-plugins)
|
||||
- [Running Plugins](#running-plugins)
|
||||
- [Global User Configuration](#global-user-configuration)
|
||||
- [Updating the Launcher](#updating-the-launcher)
|
||||
- [Minimal Required Launcher Version](#minimal-required-launcher-version)
|
||||
- [Downloading Launcher Releases](#downloading-launcher-releases)
|
||||
- [Minimal Required Launcher Version](#minimal-required-launcher-version)
|
||||
- [Downloading Launcher Releases](#downloading-launcher-releases)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
|
@ -14,6 +14,7 @@ containing robust and descriptive parser errors directly in the AST.
|
||||
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||
|
||||
- [Functionality](#functionality)
|
||||
- [Generation](#generation)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
@ -40,3 +41,111 @@ Each node should contain:
|
||||
>
|
||||
> - Flesh out the design for the AST based on the requirements of the various
|
||||
> parser phases.
|
||||
|
||||
## Generation
|
||||
|
||||
The single source of truth for the AST is its Rust implementation. Therefore, in
|
||||
order to not get out of sync, the Scala AST implementation is generated during
|
||||
compilation directly from the Rust ast source file.
|
||||
|
||||
The command for generating the Scala ast and storing it in the file
|
||||
`foo/ast.scala` is following:
|
||||
`cargo run -p ast -- --generate-scala-ast foo/ast.scala`.
|
||||
|
||||
Since there isn't 1:1 mapping between Rust and Scala, only a subset of Rust's
|
||||
structures is supported. These are follows.
|
||||
|
||||
##### Primitives
|
||||
|
||||
```
|
||||
u32 | i32 | u16 | i16 | i8 => Int,
|
||||
usize | isize | u64 | i64 => Long,
|
||||
u8 => Byte,
|
||||
char => Char,
|
||||
Vec => Vector,
|
||||
```
|
||||
|
||||
*Note: It is assumed, that Enso runs on 64bit platforms. Therefore, `usize` and
|
||||
`isize` are converted to `Long`.*
|
||||
|
||||
##### Structures With Named Fields
|
||||
|
||||
```
|
||||
struct Foo<X> { x: X<z::Y>, .. }
|
||||
```
|
||||
|
||||
Is converted into:
|
||||
|
||||
```
|
||||
case class Foo[X](x: X[Z.Y], ..)
|
||||
```
|
||||
|
||||
##### Enums With Named Fields
|
||||
|
||||
```
|
||||
enum Foo{ Bar{x:X}, Baz{y:Y, z:Z} }
|
||||
```
|
||||
|
||||
Is converted into:
|
||||
|
||||
```
|
||||
sealed trait Foo
|
||||
case class Bar(x:X) extends Foo
|
||||
case class Baz(y:Y, z:Z) extends Foo
|
||||
```
|
||||
|
||||
##### Enums With One Unnamed Qualified Field
|
||||
|
||||
```
|
||||
enum Enum { Foo(x::Foo), Bar(x::Bar), Baz(y::Baz) }
|
||||
|
||||
mod x {
|
||||
pub struct Foo { .. }
|
||||
pub struct Bar { .. }
|
||||
}
|
||||
|
||||
mod y {
|
||||
pub struct Baz { .. }
|
||||
}
|
||||
```
|
||||
|
||||
Is converted into:
|
||||
|
||||
```
|
||||
sealed trait Enum
|
||||
|
||||
object X {
|
||||
sealed trait X extends Enum
|
||||
case class Foo(..) extends X
|
||||
case class Bar(..) extends X
|
||||
}
|
||||
|
||||
object Y {
|
||||
sealed trait Y extends Enum
|
||||
case class Baz(..) extends Y
|
||||
}
|
||||
```
|
||||
|
||||
##### Modules
|
||||
|
||||
```
|
||||
mod foo { .. }
|
||||
```
|
||||
|
||||
Is converted into:
|
||||
|
||||
```
|
||||
object Foo { sealed trait Foo; .. }
|
||||
```
|
||||
|
||||
##### Type Aliases
|
||||
|
||||
```
|
||||
type A<X> = B<X,Y>;
|
||||
```
|
||||
|
||||
Is converted into:
|
||||
|
||||
```
|
||||
type A[X] = B[X, Y]
|
||||
```
|
||||
|
@ -10,7 +10,7 @@ homepage = "https://github.com/enso-org/enso/lib/rust/ast"
|
||||
repository = "https://github.com/enso-org/enso"
|
||||
license-file = "../../../LICENSE"
|
||||
|
||||
keywords = ["ast"]
|
||||
keywords = ["ast"]
|
||||
categories = ["parsing"]
|
||||
|
||||
publish = false
|
||||
@ -22,4 +22,7 @@ test = true
|
||||
bench = true
|
||||
|
||||
[dependencies]
|
||||
uuid = { version = "0.8.1", features = ["serde","v4","wasm-bindgen"] }
|
||||
clap = { version = "2.33.1" }
|
||||
itertools = { version = "0.9.0" }
|
||||
syn = { version = "1.0.12", features = ["full", "extra-traits", "visit-mut", "visit"] }
|
||||
uuid = { version = "0.8.1" , features = ["serde","v4","wasm-bindgen"] }
|
||||
|
296
lib/rust/ast/src/ast.rs
Normal file
296
lib/rust/ast/src/ast.rs
Normal file
@ -0,0 +1,296 @@
|
||||
//! This module exports the implementation of the enso abstract syntax tree.
|
||||
|
||||
use app::*;
|
||||
use lines::*;
|
||||
use def::*;
|
||||
use name::*;
|
||||
use invalid::*;
|
||||
use num::*;
|
||||
use txt::*;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
|
||||
// ===================================
|
||||
// === Abstract Syntax Tree (Stub) ===
|
||||
// ===================================
|
||||
|
||||
/// An ast node of unknown shape.
|
||||
pub type AnyAst = Ast<Shape>;
|
||||
|
||||
/// An ast node with an unique id and length.
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Ast<Shape> {
|
||||
/// A unique identifier.
|
||||
uid: Option<Uuid>,
|
||||
/// Length in number of chars of this ast node.
|
||||
len: usize,
|
||||
/// The number of trailing spaces.
|
||||
off: usize,
|
||||
/// The ast node itself.
|
||||
ast: Shape,
|
||||
}
|
||||
|
||||
// The set of all ast nodes.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum Shape {
|
||||
Unrecognized(invalid::Unrecognized),
|
||||
Blank(name::Blank),
|
||||
Var(name::Var),
|
||||
Cons(name::Cons),
|
||||
Opr(name::Opr),
|
||||
Number(num::Number),
|
||||
Text(txt::Text),
|
||||
Prefix(app::Prefix),
|
||||
Infix(app::Infix),
|
||||
Module(lines::Module),
|
||||
Block(lines::Block),
|
||||
FunDef(def::FunDef),
|
||||
OprDef(def::OprDef),
|
||||
VarDef(def::VarDef),
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Application ===
|
||||
// ===================
|
||||
|
||||
/// This module exports ast shapes that represent function application.
|
||||
pub mod app {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for application.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Prefix {
|
||||
pub func : Box<AnyAst>,
|
||||
pub arg : Box<AnyAst>,
|
||||
}
|
||||
|
||||
/// The ast node for an infix operator application.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Infix {
|
||||
pub larg: Box<AnyAst>,
|
||||
pub opr: Box<Ast<name::Opr>>,
|
||||
pub rarg: Box<AnyAst>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === Block & Module ===
|
||||
// ======================
|
||||
|
||||
/// This module exports ast shapes that are represented as sequence of equally indented lines.
|
||||
pub mod lines {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for a module that represents the file's root block.
|
||||
///
|
||||
/// The module consists of a sequence of possibly empty lines with no leading indentation.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Module { pub lines: Vec<Option<AnyAst>> }
|
||||
|
||||
/// The ast node for a block that represents a sequence of equally indented lines.
|
||||
///
|
||||
/// Lines may contain some child ast or be empty. Block is used for all code blocks except for
|
||||
/// the root one, which uses `Module`.
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Block {
|
||||
/// Absolute's block indent, counting from the module's root.
|
||||
pub indent: usize,
|
||||
/// Leading empty lines. Each line is represented by absolute count of spaces
|
||||
/// it contains, counting from the root.
|
||||
pub empty_lines: Vec<usize>,
|
||||
/// First line with non-empty item.
|
||||
pub first_line: Box<AnyAst>,
|
||||
/// Rest of lines, each of them optionally having contents.
|
||||
pub lines: Vec<Option<AnyAst>>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Definition ===
|
||||
// ==================
|
||||
|
||||
/// This module exports ast shapes that represent definition of variable, function etc.
|
||||
pub mod def {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for a method definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct FunDef {
|
||||
pub name: Box<Ast<name::Var>>,
|
||||
pub args: Vec<AnyAst>,
|
||||
pub body: Box<AnyAst>,
|
||||
}
|
||||
|
||||
/// The ast node for an operator definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct OprDef {
|
||||
pub name: Box<Ast<name::Opr>>,
|
||||
pub args: Vec<AnyAst>,
|
||||
pub body: Box<AnyAst>,
|
||||
}
|
||||
|
||||
/// The ast node for a variable definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct VarDef {
|
||||
pub name: Box<Ast<name::Var>>,
|
||||
pub value: Box<AnyAst>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Identifiers ===
|
||||
// ===================
|
||||
|
||||
/// This module exports ast shapes for basic identifiers.
|
||||
pub mod name {
|
||||
/// The ast node for the underscore `_`.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub struct Blank { }
|
||||
|
||||
/// The ast node for a variable.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Var { pub name: String }
|
||||
|
||||
/// The ast node for a constructor.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Cons { pub name: String }
|
||||
|
||||
/// The ast node for an operator.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Opr { pub name: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Invalid ===
|
||||
// ===============
|
||||
|
||||
/// This module exports invalid ast shapes.
|
||||
pub mod invalid {
|
||||
/// Unrecognized token.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Unrecognized { pub str: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Number ===
|
||||
// ==============
|
||||
|
||||
/// This module exports ast shapes that represent numbers.
|
||||
pub mod num {
|
||||
/// The ast node for a number.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Number { pub number: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Text ===
|
||||
// ============
|
||||
|
||||
|
||||
/// This module exports ast shapes that represent text (strings).
|
||||
pub mod txt {
|
||||
/// The ast node for a string of text.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Text { pub text: String }
|
||||
}
|
||||
|
||||
// === Into<Shape> ===
|
||||
|
||||
impl From<Unrecognized> for Shape { fn from(val:Unrecognized) -> Self { Self::Unrecognized(val) } }
|
||||
impl From<Blank> for Shape { fn from(val:Blank) -> Self { Self::Blank (val) } }
|
||||
impl From<Var> for Shape { fn from(val:Var) -> Self { Self::Var (val) } }
|
||||
impl From<Cons> for Shape { fn from(val:Cons) -> Self { Self::Cons (val) } }
|
||||
impl From<Opr> for Shape { fn from(val:Opr) -> Self { Self::Opr (val) } }
|
||||
impl From<Number> for Shape { fn from(val:Number) -> Self { Self::Number (val) } }
|
||||
impl From<Text> for Shape { fn from(val: Text) -> Self { Self::Text (val) } }
|
||||
impl From<Prefix> for Shape { fn from(val:Prefix) -> Self { Self::Prefix (val) } }
|
||||
impl From<Infix> for Shape { fn from(val:Infix) -> Self { Self::Infix (val) } }
|
||||
impl From<Module> for Shape { fn from(val:Module) -> Self { Self::Module (val) } }
|
||||
impl From<Block> for Shape { fn from(val:Block) -> Self { Self::Block (val) } }
|
||||
impl From<FunDef> for Shape { fn from(val:FunDef) -> Self { Self::FunDef (val) } }
|
||||
impl From<OprDef> for Shape { fn from(val:OprDef) -> Self { Self::OprDef (val) } }
|
||||
impl From<VarDef> for Shape { fn from(val:VarDef) -> Self { Self::VarDef (val) } }
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Constructors ===
|
||||
// ====================
|
||||
|
||||
impl AnyAst {
|
||||
/// Creates a new ast node with random `Uuid` from `Shape`.
|
||||
pub fn new(ast:impl Into<Shape>) -> Self {
|
||||
Self {ast:ast.into(), uid: Some(Uuid::new_v4()), len:0, off:0 }
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Unrecognized`.
|
||||
pub fn unrecognized(str:String) -> Self {
|
||||
Self::new(Unrecognized{str})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Blank`.
|
||||
pub fn blank() -> Self {
|
||||
Self::new(Blank{})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Var`.
|
||||
pub fn var(name:String) -> Self {
|
||||
Self::new(Var{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Cons`.
|
||||
pub fn cons(name:String) -> Self {
|
||||
Self::new(Cons{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Opr`.
|
||||
pub fn opr(name:String) -> Self {
|
||||
Self::new(Opr{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Number`.
|
||||
pub fn num(number:i64) -> Self {
|
||||
Self::new(Number{number:number.to_string()})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Text`.
|
||||
pub fn text(text:String) -> Self {
|
||||
Self::new(Text{text})
|
||||
}
|
||||
}
|
332
lib/rust/ast/src/generation.rs
Normal file
332
lib/rust/ast/src/generation.rs
Normal file
@ -0,0 +1,332 @@
|
||||
//! This module exports scala ast generator.
|
||||
|
||||
#![allow(unused_must_use)]
|
||||
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use syn;
|
||||
use syn::Ident;
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Scala Generator ===
|
||||
// =======================
|
||||
|
||||
/// A Scala ast generator.
|
||||
#[derive(Debug,Clone,Default)]
|
||||
pub struct ScalaGenerator {
|
||||
/// The content of the file.
|
||||
code: String,
|
||||
/// Current indentation.
|
||||
indent: usize,
|
||||
/// Inheritance hierarchy.
|
||||
extends: HashMap<Ident,Ident>
|
||||
}
|
||||
|
||||
impl ScalaGenerator {
|
||||
/// Generates a Scala ast from `lib/rust/ast/src/lib.rs`.
|
||||
pub fn ast() -> std::io::Result<String> {
|
||||
let mut content = String::new();
|
||||
let mut file = File::open("lib/rust/ast/src/ast.rs")?;
|
||||
file.read_to_string(&mut content);
|
||||
|
||||
Ok(Self::file(syn::parse_file(content.as_str()).unwrap()))
|
||||
}
|
||||
|
||||
/// Generates a Scala ast definition from a parsed Rust ast definition.
|
||||
pub fn file(file:syn::File) -> String {
|
||||
let mut this = Self::default();
|
||||
this.block(&file.items[..]);
|
||||
this.code
|
||||
}
|
||||
|
||||
/// Generates a block of Scala code.
|
||||
fn block(&mut self, lines:&[syn::Item]) {
|
||||
for item in lines {
|
||||
match item {
|
||||
syn::Item::Enum (val) => self.adt(&val),
|
||||
syn::Item::Type (val) => {
|
||||
write!(self.code, "\n{:i$}type ", "", i=self.indent);
|
||||
self.typ_name(&val.ident);
|
||||
self.generics(&val.generics);
|
||||
write!(self.code, " = ");
|
||||
self.typ(val.ty.as_ref());
|
||||
writeln!(self.code, "");
|
||||
}
|
||||
syn::Item::Struct(val) => {
|
||||
if let syn::Fields::Named(fields) = &val.fields {
|
||||
self.class(&val.ident, &val.generics, fields);
|
||||
} else {
|
||||
panic!("All struct fields must be named!");
|
||||
}
|
||||
}
|
||||
syn::Item::Mod(val) => {
|
||||
write!(self.code, "\n{:i$}object " , "", i=self.indent);
|
||||
self.typ_name(&val.ident);
|
||||
writeln!(self.code, " {{");
|
||||
self.indent += 4;
|
||||
write!(self.code, "{:i$}sealed trait ", "", i=self.indent);
|
||||
self.typ_name(&val.ident);
|
||||
self.extends(&val.ident);
|
||||
if let Some(content) = &val.content {
|
||||
self.block(&content.1[..]);
|
||||
};
|
||||
self.indent -= 4;
|
||||
writeln!(self.code, "{:i$}}}", "", i=self.indent);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Scala case class.
|
||||
///
|
||||
/// `struct Foo { bar:Bar, baz:Baz }` => `case class Foo(bar:Bar, baz:Baz)`
|
||||
fn class(&mut self, ident:&Ident, generics:&syn::Generics, fields:&syn::FieldsNamed) {
|
||||
write!(self.code, "{:i$}case class ", "", i=self.indent);
|
||||
self.typ_name(ident);
|
||||
self.generics(generics);
|
||||
write!(self.code, "(");
|
||||
for (i, field) in fields.named.iter().enumerate() {
|
||||
if i != 0 { write!(self.code, ", "); }
|
||||
if let Some(ident) = &field.ident {
|
||||
self.var_name(&ident);
|
||||
}
|
||||
write!(self.code, ": ");
|
||||
self.typ(&field.ty);
|
||||
}
|
||||
write!(self.code, ")");
|
||||
self.extends(ident);
|
||||
}
|
||||
|
||||
/// Generates Scala ADT - case classes extending a sealed trait.
|
||||
///
|
||||
/// There are two modes of conversion:
|
||||
///
|
||||
/// 1) When the Rust enum variant has named fields:
|
||||
/// ```
|
||||
/// enum Foo { Bar{x:isize}, Baz{y:isize} }
|
||||
/// ```
|
||||
/// ===>
|
||||
/// ```scala
|
||||
/// sealed trait Foo
|
||||
/// case class Bar(x:Int) extends Foo
|
||||
/// case class Baz(y:Int) extends Foo
|
||||
/// ```
|
||||
///
|
||||
/// 2) When the Rust enum variant has one unnamed field with qualified type:
|
||||
/// ```
|
||||
/// enum Foo { Bar(barz::Bar), Baz(barz::Baz) }
|
||||
/// mod barz {
|
||||
/// pub struct Bar { }
|
||||
/// pub struct Baz {y:isize}
|
||||
/// }
|
||||
/// ```
|
||||
/// ===>
|
||||
/// ```scala
|
||||
/// sealed trait Foo
|
||||
/// object barz {
|
||||
/// sealed trait Barz extends Foo
|
||||
/// case class Bar() extends Barz
|
||||
/// case class Baz(y:size) extends Barz
|
||||
/// }
|
||||
/// ```
|
||||
fn adt(&mut self, adt:&syn::ItemEnum) {
|
||||
write!(self.code, "\n{:i$}sealed trait {}", "", adt.ident, i=self.indent);
|
||||
self.generics(&adt.generics);
|
||||
self.extends(&adt.ident);
|
||||
for variant in &adt.variants {
|
||||
match &variant.fields {
|
||||
syn::Fields::Named (fields) => {
|
||||
self.extends.insert(variant.ident.clone(), adt.ident.clone());
|
||||
self.class(&variant.ident, &adt.generics, fields);
|
||||
},
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
if let Some(syn::Type::Path(path)) = fields.unnamed.first().map(|f| &f.ty) {
|
||||
let path = path.path.segments.iter().rev().take(2).collect_tuple();
|
||||
if let Some((class, object)) = path {
|
||||
self.extends.insert(object.ident.clone(), adt.ident.clone());
|
||||
self.extends.insert(class.ident.clone(), object.ident.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates Scala class extension.
|
||||
///
|
||||
/// `foo` => `extends Foo`
|
||||
fn extends(&mut self, ident:&Ident) {
|
||||
if let Some(name) = self.extends.get(&ident).cloned() {
|
||||
write!(self.code, " extends ");
|
||||
self.typ_name(&name);
|
||||
}
|
||||
writeln!(self.code, "");
|
||||
}
|
||||
|
||||
/// Generates Scala type parameters.
|
||||
///
|
||||
/// `<Foo, Bar>` = `[Foo, Bar]`
|
||||
fn generics(&mut self, generics:&syn::Generics) {
|
||||
if generics.params.is_empty() {return}
|
||||
write!(self.code, "[");
|
||||
for (i, param) in generics.params.iter().enumerate() {
|
||||
if i != 0 { write!(self.code, ", "); }
|
||||
match param {
|
||||
syn::GenericParam::Type(typ) => self.typ_name(&typ.ident),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
write!(self.code, "]");
|
||||
}
|
||||
|
||||
/// Generates a qualified scala type with type arguments.
|
||||
///
|
||||
/// `foo::Bar<Baz>` => `Foo.Bar[Baz]`
|
||||
fn typ(&mut self, typ:&syn::Type) {
|
||||
match typ {
|
||||
syn::Type::Path(path) => {
|
||||
for (i, typ) in path.path.segments.iter().enumerate() {
|
||||
if i != 0 { write!(self.code, "."); }
|
||||
self.typ_segment(typ);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Scala type with type arguments.
|
||||
///
|
||||
/// `Foo<Bar<Baz>>` => `Foo[Bar[Baz]]`
|
||||
fn typ_segment(&mut self, typ:&syn::PathSegment) {
|
||||
let boxed = typ.ident.to_string().as_str() == "Box";
|
||||
if !boxed { self.typ_name(&typ.ident); }
|
||||
match &typ.arguments {
|
||||
syn::PathArguments::AngleBracketed(typ) => {
|
||||
if !boxed { write!(self.code, "["); }
|
||||
for (i, typ) in typ.args.iter().enumerate() {
|
||||
if i != 0 { write!(self.code, ", "); }
|
||||
match typ {
|
||||
syn::GenericArgument::Type(typ) => self.typ(typ),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if !boxed { write!(self.code, "]"); }
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Scala variable name (camel case).
|
||||
///
|
||||
/// `foo_bar` => `fooBar`
|
||||
fn var_name(&mut self, ident:&Ident) {
|
||||
let mut underscore = false;
|
||||
for char in ident.to_string().chars() {
|
||||
if char == '_' {
|
||||
underscore = true ;
|
||||
} else if underscore {
|
||||
underscore = false;
|
||||
for char in char.to_uppercase() {
|
||||
self.code.push(char)
|
||||
}
|
||||
} else {
|
||||
self.code.push(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a Scala type name.
|
||||
///
|
||||
/// The following Rust types are automatically converted to Scala types:
|
||||
/// ```code
|
||||
/// u32 | i32 | u16 | i16 | i8 => Int,
|
||||
/// usize | isize | u64 | i64 => Long,
|
||||
/// u8 => Byte,
|
||||
/// char => Char,
|
||||
/// Vec => Vector,
|
||||
/// ```
|
||||
fn typ_name(&mut self, ident:&Ident) {
|
||||
let name = match ident.to_string().as_str() {
|
||||
"u32" | "i32" | "u16" | "i16" | "i8" => "Int",
|
||||
"usize" | "isize" | "u64" | "i64" => "Long",
|
||||
"u8" => "Byte",
|
||||
"char" => "Char",
|
||||
"Vec" => "Vector",
|
||||
name => {
|
||||
let mut chars = name.chars();
|
||||
if let Some(char) = chars.next() {
|
||||
write!(self.code, "{}", char.to_uppercase().to_string() + chars.as_str());
|
||||
}
|
||||
""
|
||||
}
|
||||
};
|
||||
write!(self.code, "{}", name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_file() {
|
||||
let rust = syn::parse_quote! {
|
||||
type A<X> = B<X,Y>;
|
||||
|
||||
pub enum FooBarBaz {
|
||||
Foo(a::Foo),
|
||||
Bar(a::Bar),
|
||||
Baz(b::Baz),
|
||||
}
|
||||
mod a {
|
||||
struct Foo {}
|
||||
struct Bar {x:usize, y:u8, z:b::Type}
|
||||
}
|
||||
mod b {
|
||||
type Type = Baz;
|
||||
|
||||
enum Baz {
|
||||
Baz1 {},
|
||||
Baz2 {foo_bar:Box<Vec<i32>>},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let scala = "
|
||||
type A[X] = B[X, Y]
|
||||
|
||||
sealed trait FooBarBaz
|
||||
|
||||
object A {
|
||||
sealed trait A extends FooBarBaz
|
||||
case class Foo() extends A
|
||||
case class Bar(x: Long, y: Byte, z: B.Type) extends A
|
||||
}
|
||||
|
||||
object B {
|
||||
sealed trait B extends FooBarBaz
|
||||
|
||||
type Type = Baz
|
||||
|
||||
sealed trait Baz extends B
|
||||
case class Baz1() extends Baz
|
||||
case class Baz2(fooBar: Vector[Int]) extends Baz
|
||||
}
|
||||
";
|
||||
assert_eq!(ScalaGenerator::file(rust), scala);
|
||||
}
|
||||
}
|
@ -10,279 +10,7 @@
|
||||
|
||||
//! This module exports the implementation of the enso abstract syntax tree.
|
||||
|
||||
use application::*;
|
||||
use block::*;
|
||||
use definition::*;
|
||||
use identifier::*;
|
||||
use invalid::*;
|
||||
use number::*;
|
||||
use text::*;
|
||||
pub mod generation;
|
||||
mod ast;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
|
||||
// ===================================
|
||||
// === Abstract Syntax Tree (Stub) ===
|
||||
// ===================================
|
||||
|
||||
/// An ast node of unknown shape.
|
||||
pub type AnyAst = Ast<Shape>;
|
||||
|
||||
/// An ast node with an unique id and length.
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Ast<Shape> {
|
||||
/// An unique identifier.
|
||||
uid: Option<Uuid>,
|
||||
/// Length in number of chars of this ast node.
|
||||
len: usize,
|
||||
/// The number of trailing spaces.
|
||||
off: usize,
|
||||
/// The ast node itself.
|
||||
ast: Shape,
|
||||
}
|
||||
|
||||
// The set of all ast nodes.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub enum Shape {
|
||||
Unrecognized(Unrecognized),
|
||||
Blank(Blank),
|
||||
Var(Var),
|
||||
Cons(Cons),
|
||||
Opr(Opr),
|
||||
Number(Number),
|
||||
Text(Text),
|
||||
Prefix(Prefix),
|
||||
Infix(Infix),
|
||||
Module(Module),
|
||||
Block(Block),
|
||||
FunDef(FunDef),
|
||||
OprDef(OprDef),
|
||||
VarDef(VarDef),
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Application ===
|
||||
// ===================
|
||||
|
||||
/// This module exports ast shapes that represent function application.
|
||||
pub mod application {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for application.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Prefix { pub func: Box<AnyAst>, pub arg: Box<AnyAst> }
|
||||
|
||||
/// The ast node for an infix operator application.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Infix { pub larg: Box<AnyAst>, pub opr: Box<Ast<Opr>>, pub rarg: Box<AnyAst> }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================
|
||||
// === Block & Module ===
|
||||
// ======================
|
||||
|
||||
/// This module exports ast shapes that are represented as sequence of equally indented lines.
|
||||
pub mod block {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for a module that represents the file's root block.
|
||||
///
|
||||
/// The module consists of a sequence of possibly empty lines with no leading indentation.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Module { pub lines: Vec<Option<AnyAst>> }
|
||||
|
||||
/// The ast node for a block that represents a sequence of equally indented lines.
|
||||
///
|
||||
/// Lines may contain some child ast or be empty. Block is used for all code blocks except for
|
||||
/// the root one, which uses `Module`.
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Block {
|
||||
/// Absolute's block indent, counting from the module's root.
|
||||
pub indent: usize,
|
||||
/// Leading empty lines. Each line is represented by absolute count of spaces
|
||||
/// it contains, counting from the root.
|
||||
pub empty_lines: Vec<usize>,
|
||||
/// First line with non-empty item.
|
||||
pub first_line: Box<AnyAst>,
|
||||
/// Rest of lines, each of them optionally having contents.
|
||||
pub lines: Vec<Option<AnyAst>>,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==================
|
||||
// === Definition ===
|
||||
// ==================
|
||||
|
||||
/// This module exports ast shapes that represent definition of variable, function etc.
|
||||
pub mod definition {
|
||||
use super::*;
|
||||
|
||||
|
||||
|
||||
/// The ast node for a method definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct FunDef { pub name: Box<Ast<Var>>, pub args: Box<AnyAst>, pub body: Box<AnyAst> }
|
||||
|
||||
/// The ast node for an operator definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct OprDef { pub name: Box<Ast<Opr>>, pub args: [Box<AnyAst>;2], pub body: Box<AnyAst> }
|
||||
|
||||
/// The ast node for a variable definition.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct VarDef { pub name: Box<Ast<Var>>, pub value: Box<AnyAst> }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Identifiers ===
|
||||
// ===================
|
||||
|
||||
/// This module exports ast shapes for basic identifiers.
|
||||
pub mod identifier {
|
||||
/// The ast node for the underscore `_`.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone,Copy)]
|
||||
pub struct Blank();
|
||||
|
||||
/// The ast node for a variable.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Var { pub name: String }
|
||||
|
||||
/// The ast node for a constructor.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Cons { pub name: String }
|
||||
|
||||
/// The ast node for an operator.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Opr { pub name: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Invalid ===
|
||||
// ===============
|
||||
|
||||
/// This module exports invalid ast shapes.
|
||||
pub mod invalid {
|
||||
/// Unrecognized token.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Unrecognized { pub str: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Number ===
|
||||
// ==============
|
||||
|
||||
/// This module exports ast shapes that represent numbers.
|
||||
pub mod number {
|
||||
/// The ast node for a number.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Number { pub number: String }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Text ===
|
||||
// ============
|
||||
|
||||
|
||||
/// This module exports ast shapes that represent text (strings).
|
||||
pub mod text {
|
||||
/// The ast node for a string of text.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Text { pub text: String }
|
||||
}
|
||||
|
||||
// === Into<Shape> ===
|
||||
|
||||
impl From<Unrecognized> for Shape { fn from(val:Unrecognized) -> Self { Self::Unrecognized(val) } }
|
||||
impl From<Blank> for Shape { fn from(val:Blank) -> Self { Self::Blank (val) } }
|
||||
impl From<Var> for Shape { fn from(val:Var) -> Self { Self::Var (val) } }
|
||||
impl From<Cons> for Shape { fn from(val:Cons) -> Self { Self::Cons (val) } }
|
||||
impl From<Opr> for Shape { fn from(val:Opr) -> Self { Self::Opr (val) } }
|
||||
impl From<Number> for Shape { fn from(val:Number) -> Self { Self::Number (val) } }
|
||||
impl From<Text> for Shape { fn from(val:Text) -> Self { Self::Text (val) } }
|
||||
impl From<Prefix> for Shape { fn from(val:Prefix) -> Self { Self::Prefix (val) } }
|
||||
impl From<Infix> for Shape { fn from(val:Infix) -> Self { Self::Infix (val) } }
|
||||
impl From<Module> for Shape { fn from(val:Module) -> Self { Self::Module (val) } }
|
||||
impl From<Block> for Shape { fn from(val:Block) -> Self { Self::Block (val) } }
|
||||
impl From<FunDef> for Shape { fn from(val:FunDef) -> Self { Self::FunDef (val) } }
|
||||
impl From<OprDef> for Shape { fn from(val:OprDef) -> Self { Self::OprDef (val) } }
|
||||
impl From<VarDef> for Shape { fn from(val:VarDef) -> Self { Self::VarDef (val) } }
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Constructors ===
|
||||
// ====================
|
||||
|
||||
impl AnyAst {
|
||||
/// Creates a new ast node with random `Uuid` from `Shape`.
|
||||
pub fn new(ast:impl Into<Shape>) -> Self {
|
||||
Self {ast:ast.into(), uid: Some(Uuid::new_v4()), len:0, off:0 }
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Unrecognized`.
|
||||
pub fn unrecognized(str:String) -> Self {
|
||||
Self::new(Unrecognized{str})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Blank`.
|
||||
pub fn blank() -> Self {
|
||||
Self::new(Blank())
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Var`.
|
||||
pub fn var(name:String) -> Self {
|
||||
Self::new(Var{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Cons`.
|
||||
pub fn cons(name:String) -> Self {
|
||||
Self::new(Cons{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Opr`.
|
||||
pub fn opr(name:String) -> Self {
|
||||
Self::new(Opr{name})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Number`.
|
||||
pub fn num(number:i64) -> Self {
|
||||
Self::new(Number{number:number.to_string()})
|
||||
}
|
||||
|
||||
/// Creates a new ast node with `Shape::Text`.
|
||||
pub fn text(text:String) -> Self {
|
||||
Self::new(Text{text})
|
||||
}
|
||||
}
|
||||
pub use crate::ast::*;
|
||||
|
22
lib/rust/ast/src/main.rs
Normal file
22
lib/rust/ast/src/main.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use ast::generation::ScalaGenerator;
|
||||
|
||||
use clap;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
|
||||
|
||||
pub fn main() -> std::io::Result<()> {
|
||||
let matches = clap::App::new("Enso AST")
|
||||
.version("1.0")
|
||||
.author("Enso Team <enso-dev@enso.org>")
|
||||
.about("Enso AST generator.")
|
||||
.args_from_usage("--generate-scala-ast [FILE] 'Generates a scala ast in specified file.'")
|
||||
.get_matches();
|
||||
|
||||
if let Some(file) = matches.value_of("generate-scala-ast") {
|
||||
File::create(file)?.write_all(ScalaGenerator::ast()?.as_bytes())?;
|
||||
println!("Generated scala ast at path: {}", file);
|
||||
}
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user