Generate Scala Classes From The Stub AST (#1044)

This commit is contained in:
Josef 2020-07-31 07:31:12 +02:00 committed by GitHub
parent cd6858a01e
commit 8bb583df5e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 800 additions and 310 deletions

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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]
```

View File

@ -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
View 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})
}
}

View 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);
}
}

View File

@ -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
View 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(())
}