mirror of
https://github.com/enso-org/enso.git
synced 2024-12-24 15:42:19 +03:00
HasSpan and HasRepr implementation for AST (#416)
Provides implementation of `HasSpan` and (newly introduced) `HasRepr` for all relevant Ast types. Implements #379. Wherever feasible, I tried to reduce boilerplate with macros. Certainly more can be done in that regard but that'd require spending more time on this task than we allocated.
This commit is contained in:
parent
05118016e1
commit
2f015c70de
@ -2,18 +2,25 @@
|
||||
#![feature(generators, generator_trait)]
|
||||
|
||||
mod internal;
|
||||
mod repr;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use ast_macros::*;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use serde::ser::{Serializer, SerializeStruct};
|
||||
use serde::de::{Deserializer, Visitor};
|
||||
use serde::de::Deserializer;
|
||||
use serde::de::Visitor;
|
||||
use serde::Deserialize;
|
||||
use serde::ser::Serializer;
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::Serialize;
|
||||
use shapely::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A sequence of AST nodes, typically the "token soup".
|
||||
pub type Stream<T> = Vec<T>;
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Errors ===
|
||||
// ==============
|
||||
@ -23,6 +30,8 @@ pub type Stream<T> = Vec<T>;
|
||||
#[derive(Display, Debug, Fail)]
|
||||
pub struct WrongEnum { pub expected_con: String }
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Tree ===
|
||||
// ============
|
||||
@ -37,6 +46,8 @@ pub struct Tree<K,V> {
|
||||
pub branches : Vec<(K, Tree<K,V>)>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Shifted ===
|
||||
// ===============
|
||||
@ -58,6 +69,7 @@ pub struct ShiftedVec1<T> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Layer ===
|
||||
// =============
|
||||
@ -75,6 +87,7 @@ impl<T> From<T> for Layered<T> {
|
||||
fn from(t: T) -> Self { Layered::layered(t) }
|
||||
}
|
||||
|
||||
|
||||
// === Layered ===
|
||||
|
||||
/// A trivial `Layer` type that is just a strongly typed wrapper over `T`.
|
||||
@ -87,6 +100,8 @@ impl<T> Layer<T> for Layered<T> {
|
||||
fn layered(t: T) -> Self { Layered(t) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Unit ===
|
||||
// ============
|
||||
@ -100,6 +115,7 @@ impl<T> Layer<T> for Layered<T> {
|
||||
#[ast_node] pub struct Unit{}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === AST ===
|
||||
// ===========
|
||||
@ -144,6 +160,7 @@ impl Ast {
|
||||
Ast::new_with_span(shape, id, span)
|
||||
}
|
||||
|
||||
/// As `new` but sets given declared span for the shape.
|
||||
pub fn new_with_span<S: Into<Shape<Ast>>>
|
||||
(shape: S, id: Option<ID>, span: usize) -> Ast {
|
||||
let shape = shape.into();
|
||||
@ -164,6 +181,12 @@ impl HasSpan for Ast {
|
||||
}
|
||||
}
|
||||
|
||||
impl HasRepr for Ast {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.wrapped.write_repr(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills `id` with `None` by default.
|
||||
impl<T: Into<Shape<Ast>>>
|
||||
From<T> for Ast {
|
||||
@ -173,7 +196,8 @@ From<T> for Ast {
|
||||
}
|
||||
}
|
||||
|
||||
// Serialization & Deserialization //
|
||||
|
||||
// === Serialization & Deserialization === //
|
||||
|
||||
/// Literals used in `Ast` serialization and deserialization.
|
||||
pub mod ast_schema {
|
||||
@ -246,6 +270,7 @@ impl<'de> Deserialize<'de> for Ast {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Shape ===
|
||||
// =============
|
||||
@ -253,7 +278,9 @@ impl<'de> Deserialize<'de> for Ast {
|
||||
/// Defines shape of the subtree. Parametrized by the child node type `T`.
|
||||
///
|
||||
/// Shape describes names of children and spacing between them.
|
||||
#[ast(flat)] pub enum Shape<T> {
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum Shape<T> {
|
||||
Unrecognized { str : String },
|
||||
InvalidQuote { quote: Builder },
|
||||
InlineBlock { quote: Builder },
|
||||
@ -314,11 +341,13 @@ impl<'de> Deserialize<'de> for Ast {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Builder ===
|
||||
// ===============
|
||||
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum Builder {
|
||||
Empty,
|
||||
Letter{char: char},
|
||||
@ -328,28 +357,37 @@ pub enum Builder {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Text ===
|
||||
// ============
|
||||
|
||||
// === Text Block Lines ===
|
||||
|
||||
#[ast] pub struct TextBlockLine<T> {
|
||||
pub empty_lines: Vec<usize>,
|
||||
pub text : Vec<T>
|
||||
}
|
||||
|
||||
#[ast(flat)] pub enum TextLine<T> {
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum TextLine<T> {
|
||||
TextLineRaw(TextLineRaw),
|
||||
TextLineFmt(TextLineFmt<T>),
|
||||
}
|
||||
|
||||
|
||||
// === Text Segments ===
|
||||
#[ast(flat)] pub enum SegmentRaw {
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum SegmentRaw {
|
||||
SegmentPlain (SegmentPlain),
|
||||
SegmentRawEscape(SegmentRawEscape),
|
||||
}
|
||||
|
||||
#[ast(flat)] pub enum SegmentFmt<T> {
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum SegmentFmt<T> {
|
||||
SegmentPlain (SegmentPlain ),
|
||||
SegmentRawEscape(SegmentRawEscape),
|
||||
SegmentExpr (SegmentExpr<T> ),
|
||||
@ -361,8 +399,12 @@ pub enum Builder {
|
||||
#[ast_node] pub struct SegmentExpr<T> { pub value: Option<T> }
|
||||
#[ast_node] pub struct SegmentEscape { pub code : Escape }
|
||||
|
||||
|
||||
// === Text Segment Escapes ===
|
||||
#[ast(flat)] pub enum RawEscape {
|
||||
|
||||
#[ast(flat)]
|
||||
#[derive(HasRepr)]
|
||||
pub enum RawEscape {
|
||||
Unfinished { },
|
||||
Invalid { str: char },
|
||||
Slash { },
|
||||
@ -370,7 +412,9 @@ pub enum Builder {
|
||||
RawQuote { },
|
||||
}
|
||||
|
||||
#[ast_node] pub enum Escape {
|
||||
#[ast]
|
||||
#[derive(HasRepr)]
|
||||
pub enum Escape {
|
||||
Character{c :char },
|
||||
Control {name :String, code: u8},
|
||||
Number {digits:String },
|
||||
@ -380,6 +424,7 @@ pub enum Builder {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Block ===
|
||||
// =============
|
||||
@ -388,6 +433,7 @@ pub enum Builder {
|
||||
#[ast] pub struct BlockLine <T> { pub elem: T, pub off: usize }
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Macro ===
|
||||
// =============
|
||||
@ -404,7 +450,6 @@ pub enum Builder {
|
||||
|
||||
pub type MacroPattern = Rc<MacroPatternRaw>;
|
||||
#[ast] pub enum MacroPatternRaw {
|
||||
|
||||
// === Boundary Patterns ===
|
||||
Begin { },
|
||||
End { },
|
||||
@ -451,9 +496,20 @@ pub enum Switch<T> { Left{value: T}, Right{value: T} }
|
||||
// Switch however does not need to be #[ast], when derive(Iterator) supports
|
||||
// enum with struct variants, this attribute should be possible to remove.
|
||||
|
||||
pub type MacroPatternMatch<T> = Rc<MacroPatternMatchRaw<T>>;
|
||||
#[ast] pub enum MacroPatternMatchRaw<T> {
|
||||
impl<T> Switch<T> {
|
||||
fn get(&self) -> &T {
|
||||
match self {
|
||||
Switch::Left (elem) => &elem.value,
|
||||
Switch::Right(elem) => &elem.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type MacroPatternMatch<T> = Rc<MacroPatternMatchRaw<T>>;
|
||||
|
||||
#[ast]
|
||||
#[derive(HasRepr)]
|
||||
pub enum MacroPatternMatchRaw<T> {
|
||||
// === Boundary Matches ===
|
||||
Begin { pat: MacroPatternRawBegin },
|
||||
End { pat: MacroPatternRawEnd },
|
||||
@ -484,10 +540,8 @@ pub type MacroPatternMatch<T> = Rc<MacroPatternMatchRaw<T>>;
|
||||
Block { pat: MacroPatternRawBlock , elem: T },
|
||||
Macro { pat: MacroPatternRawMacro , elem: T },
|
||||
Invalid { pat: MacroPatternRawInvalid , elem: T },
|
||||
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// === Spaceless AST ===========================================================
|
||||
// =============================================================================
|
||||
@ -522,6 +576,7 @@ pub type MacroPatternMatch<T> = Rc<MacroPatternMatchRaw<T>>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===========
|
||||
// === AST ===
|
||||
// ===========
|
||||
@ -530,9 +585,20 @@ pub type MacroPatternMatch<T> = Rc<MacroPatternMatchRaw<T>>;
|
||||
|
||||
/// Things that can be asked about their span.
|
||||
pub trait HasSpan {
|
||||
/// Length of the textual representation of This type in Unicode codepoints.
|
||||
///
|
||||
/// Usually implemented together with `HasSpan`.For any `T:HasSpan+HasRepr`
|
||||
/// for `t:T` the following must hold: `t.span() == t.repr().len()`.
|
||||
fn span(&self) -> usize;
|
||||
}
|
||||
|
||||
/// Counts codepoints.
|
||||
impl HasSpan for char {
|
||||
fn span(&self) -> usize {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts codepoints.
|
||||
impl HasSpan for String {
|
||||
fn span(&self) -> usize {
|
||||
@ -547,6 +613,130 @@ impl HasSpan for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasSpan> HasSpan for Option<T> {
|
||||
fn span(&self) -> usize {
|
||||
self.as_ref().map_or(0, |wrapped| wrapped.span())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasSpan> HasSpan for Vec<T> {
|
||||
fn span(&self) -> usize {
|
||||
let spans = self.iter().map(|elem| elem.span());
|
||||
spans.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasSpan> HasSpan for Rc<T> {
|
||||
fn span(&self) -> usize {
|
||||
self.deref().span()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasSpan, U: HasSpan> HasSpan for (T,U) {
|
||||
fn span(&self) -> usize {
|
||||
self.0.span() + self.1.span()
|
||||
}
|
||||
}
|
||||
impl<T: HasSpan, U: HasSpan, V: HasSpan> HasSpan for (T,U,V) {
|
||||
fn span(&self) -> usize {
|
||||
self.0.span() + self.1.span() + self.2.span()
|
||||
}
|
||||
}
|
||||
impl HasSpan for usize {
|
||||
fn span(&self) -> usize {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl<T: HasSpan> HasSpan for &T {
|
||||
fn span(&self) -> usize {
|
||||
self.deref().span()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === HasRepr ===
|
||||
|
||||
/// Things that can be asked about their textual representation.
|
||||
///
|
||||
/// See also `HasSpan`.
|
||||
pub trait HasRepr {
|
||||
/// Obtain the text representation for the This type.
|
||||
fn repr(&self) -> String {
|
||||
let mut acc = String::new();
|
||||
self.write_repr(&mut acc);
|
||||
acc
|
||||
}
|
||||
|
||||
fn write_repr(&self, target:&mut String);
|
||||
}
|
||||
|
||||
impl HasRepr for char {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
target.push(*self);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasRepr for String {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
target.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasRepr for &str {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
target.push_str(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for Option<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
for el in self.iter() {
|
||||
el.write_repr(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for Vec<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
for el in self.iter() {
|
||||
el.write_repr(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: HasRepr> HasRepr for Rc<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.deref().write_repr(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr, U: HasRepr> HasRepr for (T,U) {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.0.write_repr(target);
|
||||
self.1.write_repr(target);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr, U: HasRepr, V: HasRepr> HasRepr for (T,U,V) {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.0.write_repr(target);
|
||||
self.1.write_repr(target);
|
||||
self.2.write_repr(target);
|
||||
}
|
||||
}
|
||||
|
||||
impl HasRepr for usize {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
target.push_str(&" ".repeat(*self));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for &T {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.deref().write_repr(target)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === WithID ===
|
||||
|
||||
pub type ID = Uuid;
|
||||
@ -585,6 +775,7 @@ where T: HasSpan {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === WithSpan ===
|
||||
|
||||
/// Stores a value of type `T` and information about its span.
|
||||
@ -626,12 +817,18 @@ impl<T> HasID for WithSpan<T>
|
||||
// TODO: the definitions below should be removed and instead generated using
|
||||
// macros, as part of https://github.com/luna/enso/issues/338
|
||||
|
||||
|
||||
// === AST ===
|
||||
|
||||
impl Ast {
|
||||
// TODO smart constructors for other cases
|
||||
// as part of https://github.com/luna/enso/issues/338
|
||||
|
||||
pub fn cons<Str: ToString>(name:Str) -> Ast {
|
||||
let cons = Cons{ name: name.to_string() };
|
||||
Ast::from(cons)
|
||||
}
|
||||
|
||||
pub fn var<Str: ToString>(name:Str) -> Ast {
|
||||
let var = Var{ name: name.to_string() };
|
||||
Ast::from(var)
|
||||
@ -662,29 +859,9 @@ impl Ast {
|
||||
}
|
||||
}
|
||||
|
||||
// === Shape ===
|
||||
|
||||
impl<T> HasSpan for Shape<T> {
|
||||
// TODO: sum spans of all members
|
||||
// as part of https://github.com/luna/enso/issues/338
|
||||
fn span(&self) -> usize {
|
||||
match self {
|
||||
Shape::Var(var) => var.span(),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Var ===
|
||||
|
||||
impl HasSpan for Var {
|
||||
fn span(&self) -> usize {
|
||||
self.name.span()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Text Conversion Boilerplate ===
|
||||
|
||||
// support for transitive conversions, like:
|
||||
// RawEscapeSth -> RawEscape -> SegmentRawEscape -> SegmentRaw
|
||||
|
||||
@ -714,7 +891,9 @@ impl From<RawQuote> for SegmentRaw {
|
||||
}
|
||||
}
|
||||
|
||||
// RawEscapeSth -> RawEscape -> SegmentRawEscape -> SegmentFmt
|
||||
|
||||
// === RawEscapeSth -> RawEscape -> SegmentRawEscape -> SegmentFmt ===
|
||||
|
||||
impl<T> From<Unfinished> for SegmentFmt<T> {
|
||||
fn from(value: Unfinished) -> Self {
|
||||
SegmentRawEscape{ code: value.into() }.into()
|
||||
@ -747,6 +926,47 @@ impl<T> From<Escape> for SegmentFmt<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === EscapeSth -> Escape -> SegmentEscape -> SegmentFmt ===
|
||||
|
||||
impl<T> From<EscapeCharacter> for SegmentFmt<T> {
|
||||
fn from(value: EscapeCharacter) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<EscapeControl> for SegmentFmt<T> {
|
||||
fn from(value: EscapeControl) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<EscapeNumber> for SegmentFmt<T> {
|
||||
fn from(value: EscapeNumber) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<EscapeUnicode16> for SegmentFmt<T> {
|
||||
fn from(value: EscapeUnicode16) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<EscapeUnicode21> for SegmentFmt<T> {
|
||||
fn from(value: EscapeUnicode21) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<EscapeUnicode32> for SegmentFmt<T> {
|
||||
fn from(value: EscapeUnicode32) -> Self {
|
||||
SegmentEscape{ code: value.into() }.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
@ -847,7 +1067,6 @@ mod tests {
|
||||
assert_eq!(strings.len(), 3);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn iterate_nested() {
|
||||
let a = Ast::var("a");
|
||||
|
551
common/rust/ast/core/src/repr.rs
Normal file
551
common/rust/ast/core/src/repr.rs
Normal file
@ -0,0 +1,551 @@
|
||||
use crate::*;
|
||||
|
||||
// ======================
|
||||
// === Token Literals ===
|
||||
// =====================
|
||||
|
||||
/// Token representing blank.
|
||||
pub const BLANK_TOKEN:char = '_';
|
||||
|
||||
/// Symbol appearing after base of the number literal.
|
||||
pub const NUMBER_BASE_SEPARATOR:char = '_';
|
||||
|
||||
/// Suffix to made a modifier from an operator
|
||||
pub const MOD_SUFFIX:char = '=';
|
||||
|
||||
/// Symbol enclosing raw Text line.
|
||||
pub const FMT_QUOTE:char = '\'';
|
||||
|
||||
/// Symbol enclosing formatted Text line.
|
||||
pub const RAW_QUOTE:char = '"';
|
||||
|
||||
/// Symbol used to break lines in Text block.
|
||||
pub const NEWLINE:char = '\n';
|
||||
|
||||
/// Symbol introducing escape segment in the Text.
|
||||
pub const BACKSLASH:char = '\\';
|
||||
|
||||
/// Symbol enclosing expression segment in the formatted Text.
|
||||
pub const EXPR_QUOTE:char = '`';
|
||||
|
||||
/// Symbol that introduces UTF-16 code in the formatted Text segment.
|
||||
pub const UNICODE16_INTRODUCER:char = 'u';
|
||||
|
||||
/// String that opens "UTF-21" code in the formatted Text segment.
|
||||
pub const UNICODE21_OPENER:&str = "u{";
|
||||
|
||||
/// String that closese "UTF-21" code in the formatted Text segment.
|
||||
pub const UNICODE21_CLOSER:&str = "}";
|
||||
|
||||
/// Symbol that introduces UTF-16 code in the formatted Text segment.
|
||||
pub const UNICODE32_INTRODUCER:char = 'U';
|
||||
|
||||
/// Quotes opening block of the raw text.
|
||||
pub const RAW_BLOCK_QUOTES:&'static str = "\"\"\"";
|
||||
|
||||
/// Quotes opening block of the formatted text.
|
||||
pub const FMT_BLOCK_QUOTES:&'static str = "'''";
|
||||
|
||||
|
||||
|
||||
// ===============
|
||||
// === Builder ===
|
||||
// ===============
|
||||
|
||||
make_repr_span!(Empty);
|
||||
make_repr_span!(Letter, self.char);
|
||||
make_repr_span!(Space , self);
|
||||
make_repr_span!(Text , self.str);
|
||||
make_repr_span!(Seq , self.first, self.second);
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === TextBlockLine ===
|
||||
// =====================
|
||||
|
||||
/// Not an instance of `HasSpan`, as it needs to know parent block's offset.
|
||||
impl<T: HasSpan> TextBlockLine<T> {
|
||||
fn span(&self, block_offset:usize) -> usize {
|
||||
let line_count = self.empty_lines.len() + 1;
|
||||
let empty_lines_space:usize = self.empty_lines.iter().sum();
|
||||
let line_breaks = line_count * NEWLINE.span();
|
||||
empty_lines_space + line_breaks + block_offset + self.text.span()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> TextBlockLine<T> {
|
||||
fn write_repr(&self, target:&mut String, block_offset:usize) {
|
||||
for empty_line_spaces in &self.empty_lines {
|
||||
(NEWLINE,empty_line_spaces).write_repr(target);
|
||||
}
|
||||
(NEWLINE,block_offset,&self.text).write_repr(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Text Segments ===
|
||||
// =====================
|
||||
|
||||
make_repr_span!(SegmentPlain , self.value);
|
||||
make_repr_span!(SegmentRawEscape, BACKSLASH, self.code );
|
||||
make_repr_span!(SegmentExpr<T> , EXPR_QUOTE, self.value, EXPR_QUOTE);
|
||||
make_repr_span!(SegmentEscape , BACKSLASH, self.code );
|
||||
|
||||
|
||||
|
||||
// =================
|
||||
// === RawEscape ===
|
||||
// =================
|
||||
|
||||
make_repr_span!(Unfinished);
|
||||
make_repr_span!(Invalid , self.str );
|
||||
make_repr_span!(Slash , BACKSLASH);
|
||||
make_repr_span!(Quote , FMT_QUOTE);
|
||||
make_repr_span!(RawQuote, RAW_QUOTE);
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Escape ===
|
||||
// ==============
|
||||
|
||||
make_repr_span!(EscapeCharacter , self.c );
|
||||
make_repr_span!(EscapeControl , self.name );
|
||||
make_repr_span!(EscapeNumber , self.digits);
|
||||
make_repr_span!(EscapeUnicode16 , UNICODE16_INTRODUCER, self.digits);
|
||||
make_repr_span!(EscapeUnicode21 , UNICODE21_OPENER , self.digits
|
||||
, UNICODE21_CLOSER);
|
||||
make_repr_span!(EscapeUnicode32 , UNICODE32_INTRODUCER, self.digits);
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Block ===
|
||||
// =============
|
||||
|
||||
make_repr_span!(BlockLine<T>, self.elem, self.off);
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Macro ===
|
||||
// =============
|
||||
|
||||
// === Macro Segments ==
|
||||
|
||||
make_repr_span!(MacroMatchSegment<T> , self.head, self.body);
|
||||
make_repr_span!(MacroAmbiguousSegment, self.head, self.body);
|
||||
|
||||
|
||||
// === MacroPatternMatch subtypes ===
|
||||
|
||||
make_repr_span!(MacroPatternMatchRawBegin );
|
||||
make_repr_span!(MacroPatternMatchRawEnd );
|
||||
make_repr_span!(MacroPatternMatchRawNothing);
|
||||
make_repr_span!(MacroPatternMatchRawSeq <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawOr <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawMany <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawExcept <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawBuild <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawErr <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawTag <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawCls <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawTok <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawBlank <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawVar <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawCons <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawOpr <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawMod <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawNum <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawText <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawBlock <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawMacro <T>, self.elem);
|
||||
make_repr_span!(MacroPatternMatchRawInvalid<T>, self.elem);
|
||||
|
||||
|
||||
// === Switch ===
|
||||
|
||||
make_repr_span!(Switch<T>, self.get());
|
||||
|
||||
|
||||
// === Shifted ===
|
||||
|
||||
make_repr_span!(Shifted<T>, self.off, self.wrapped);
|
||||
make_repr_span!(ShiftedVec1<T>, self.head, self.tail);
|
||||
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// === Shape ===================================================================
|
||||
// =============================================================================
|
||||
|
||||
// ===============
|
||||
// === Invalid ===
|
||||
// ===============
|
||||
|
||||
make_repr_span!(Unrecognized, self.str );
|
||||
make_repr_span!(InvalidQuote, self.quote);
|
||||
make_repr_span!(InlineBlock , self.quote);
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Identifiers ===
|
||||
// ===================
|
||||
|
||||
make_repr_span!(Blank , BLANK_TOKEN);
|
||||
make_repr_span!(Var , self.name );
|
||||
make_repr_span!(Cons , self.name );
|
||||
make_repr_span!(Opr , self.name );
|
||||
make_repr_span!(Mod , self.name, MOD_SUFFIX );
|
||||
make_repr_span!(InvalidSuffix<T>, self.elem, self.suffix);
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Number ===
|
||||
// ==============
|
||||
|
||||
/// Helper to represent that optional number base has additional character.
|
||||
struct NumberBase<T>(T);
|
||||
make_repr_span!(NumberBase<T>, self.0, NUMBER_BASE_SEPARATOR);
|
||||
make_repr_span!(Number , self.base.as_ref().map(|b| NumberBase(b))
|
||||
, self.int);
|
||||
make_repr_span!(DanglingBase , self.base, NUMBER_BASE_SEPARATOR);
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
// === Text ===
|
||||
// ============
|
||||
|
||||
// === Indented ===
|
||||
|
||||
/// Helper to represent line with additional spacing prepended.
|
||||
struct Indented<T>(usize,T);
|
||||
make_repr_span!(Indented<T>, self.0, self.1);
|
||||
impl<T> Block<T> {
|
||||
fn indented<'t, U>(&self, t:&'t U) -> Indented<&'t U> {
|
||||
Indented(self.indent,t)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Lines ===
|
||||
|
||||
make_repr_span!(TextLineRaw , RAW_QUOTE, self.text, RAW_QUOTE);
|
||||
make_repr_span!(TextLineFmt<T> , FMT_QUOTE, self.text, FMT_QUOTE);
|
||||
|
||||
|
||||
// === TextBlockRaw ==
|
||||
|
||||
impl HasSpan for TextBlockRaw {
|
||||
fn span(&self) -> usize {
|
||||
let mut acc = (RAW_BLOCK_QUOTES,self.spaces).span();
|
||||
for line in self.text.iter() {
|
||||
acc += line.span(self.offset);
|
||||
}
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
impl HasRepr for TextBlockRaw {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
(RAW_BLOCK_QUOTES, self.spaces).write_repr(target);
|
||||
for line in self.text.iter() {
|
||||
line.write_repr(target, self.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === TextBlockFmt ==
|
||||
|
||||
impl<T: HasSpan> HasSpan for TextBlockFmt<T> {
|
||||
fn span(&self) -> usize {
|
||||
let lines = self.text.iter();
|
||||
let line_spans = lines.map(|line| line.span(self.offset));
|
||||
let lines_span:usize = line_spans.sum();
|
||||
FMT_BLOCK_QUOTES.span() + self.spaces + lines_span
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for TextBlockFmt<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
(FMT_BLOCK_QUOTES,self.spaces).write_repr(target);
|
||||
for line in self.text.iter() {
|
||||
line.write_repr(target,self.offset);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === TextUnclosed ==
|
||||
|
||||
// TODO: [mwu] `TextUnclosed<T>` as it needs to cut off closing quote from the
|
||||
// stored text line. Likely this type should be stored like this.
|
||||
impl<T: HasSpan> HasSpan for TextUnclosed<T> {
|
||||
fn span(&self) -> usize {
|
||||
self.line.span() - 1 // remove missing quote
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for TextUnclosed<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
self.line.write_repr(target);
|
||||
target.pop(); // remove missing quote
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ====================
|
||||
// === Applications ===
|
||||
// ====================
|
||||
|
||||
make_repr_span!(Prefix <T>, self.func, self.off, self.arg);
|
||||
make_repr_span!(Infix <T>, self.larg, self.loff, self.opr, self.roff
|
||||
, self.rarg);
|
||||
make_repr_span!(SectionLeft <T>, self.arg, self.off, self.opr);
|
||||
make_repr_span!(SectionRight<T>, self.opr, self.off, self.arg);
|
||||
make_repr_span!(SectionSides<T>, self.opr);
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Module ===
|
||||
// ==============
|
||||
|
||||
// === Module ==
|
||||
|
||||
impl<T: HasSpan> HasSpan for Module<T> {
|
||||
fn span(&self) -> usize {
|
||||
assert!(self.lines.len() > 0);
|
||||
let break_count = self.lines.len() - 1;
|
||||
let breaks_span = break_count * NEWLINE.span();
|
||||
let lines_span = self.lines.span();
|
||||
lines_span + breaks_span
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for Module<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
let mut iter = self.lines.iter();
|
||||
if let Some(first_line) = iter.next() {
|
||||
first_line.write_repr(target)
|
||||
}
|
||||
for line in iter {
|
||||
(NEWLINE,line).write_repr(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Block ==
|
||||
|
||||
impl<T: HasSpan> HasSpan for Block<T> {
|
||||
fn span(&self) -> usize {
|
||||
let line_span = |line:&BlockLine<Option<T>>| {
|
||||
let indent = line.elem.as_ref().map_or(0, |_| self.indent);
|
||||
NEWLINE.span() + indent + line.span()
|
||||
};
|
||||
let head_span = if self.is_orphan { 0 } else { 1 };
|
||||
let empty_lines = self.empty_lines.span() + self.empty_lines.len();
|
||||
let first_line = self.indent + self.first_line.span();
|
||||
let lines = self.lines.iter().map(line_span).sum::<usize>();
|
||||
head_span + empty_lines + first_line + lines
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: HasRepr> HasRepr for Block<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
(!self.is_orphan).as_some(NEWLINE).write_repr(target);
|
||||
for empty_line_space in &self.empty_lines {
|
||||
(empty_line_space,NEWLINE).write_repr(target);
|
||||
}
|
||||
self.indented(&self.first_line).write_repr(target);
|
||||
for line in &self.lines {
|
||||
(NEWLINE,self.indented(line)).write_repr(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ==============
|
||||
// === Macros ===
|
||||
// ==============
|
||||
|
||||
// === Match ==
|
||||
|
||||
make_span!(Match<T>, self.pfx, self.segs);
|
||||
|
||||
impl<T: HasRepr> HasRepr for Match<T> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
for pat_match in &self.pfx {
|
||||
for sast in pat_match.iter() {
|
||||
// reverse the order for prefix: ast before spacing
|
||||
(&sast.wrapped,&sast.off).write_repr(target);
|
||||
}
|
||||
}
|
||||
self.segs.write_repr(target);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// === Ambiguous ===
|
||||
|
||||
make_repr_span!(Ambiguous, self.segs);
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Spaceless AST ===
|
||||
// =====================
|
||||
|
||||
not_supported_repr!(Comment);
|
||||
not_supported_repr!(Import<T>);
|
||||
not_supported_repr!(Mixfix<T>);
|
||||
not_supported_repr!(Group<T>);
|
||||
not_supported_repr!(Def<T>);
|
||||
not_supported_repr!(Foreign);
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
||||
/// Tests for spacelesss AST. Other AST is covered by parsing tests that verify
|
||||
/// that correct spans and text representation are generated. Only spaceless AST
|
||||
/// is not returned by the parser and can't be covered in this way.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// === Comment ===
|
||||
|
||||
fn make_comment() -> Shape<Ast> {
|
||||
Comment {lines:vec![]}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn comment_panics_on_repr() {
|
||||
make_comment().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn comment_panics_on_span() {
|
||||
make_comment().span();
|
||||
}
|
||||
|
||||
|
||||
// === Import ===
|
||||
|
||||
fn make_import() -> Shape<Ast> {
|
||||
Import {path : vec![]}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn import_panics_on_repr() {
|
||||
make_import().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn import_panics_on_span() {
|
||||
make_import().span();
|
||||
}
|
||||
|
||||
|
||||
// === Mixfix ===
|
||||
|
||||
fn make_mixfix() -> Shape<Ast> {
|
||||
Mixfix {
|
||||
name : vec![],
|
||||
args : vec![]
|
||||
}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn mixfix_panics_on_repr() {
|
||||
make_mixfix().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn mixfix_panics_on_span() {
|
||||
make_mixfix().span();
|
||||
}
|
||||
|
||||
|
||||
// === Group ===
|
||||
|
||||
fn make_group() -> Shape<Ast> {
|
||||
Group {body : None}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn group_panics_on_repr() {
|
||||
make_group().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn group_panics_on_span() {
|
||||
make_group().span();
|
||||
}
|
||||
|
||||
|
||||
// === Def ===
|
||||
|
||||
fn make_def() -> Shape<Ast> {
|
||||
Def {
|
||||
name : Ast::cons("Foo"),
|
||||
args : vec![],
|
||||
body : None
|
||||
}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn def_panics_on_repr() {
|
||||
make_def().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn def_panics_on_span() {
|
||||
make_def().span();
|
||||
}
|
||||
|
||||
// === Foreign ===
|
||||
|
||||
fn make_foreign() -> Shape<Ast> {
|
||||
Foreign {
|
||||
indent : 0,
|
||||
lang : "Python".into(),
|
||||
code : vec![]
|
||||
}.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn foreign_panics_on_repr() {
|
||||
make_foreign().repr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn foreign_panics_on_span() {
|
||||
make_foreign().span();
|
||||
}
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Helper macros used when defining AST structures.
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
mod repr;
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use proc_macro2::{TokenStream, Ident, Span};
|
||||
@ -7,6 +13,7 @@ use quote::quote;
|
||||
use syn;
|
||||
|
||||
use macro_utils::{gather_all_type_reprs, repr};
|
||||
use crate::repr::ReprDescription;
|
||||
|
||||
// ==============
|
||||
// === Macros ===
|
||||
@ -197,15 +204,12 @@ fn gen_from_impls
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Rewrites enum definition by creating a new type for each constructor.
|
||||
///
|
||||
/// Each nested constructor will be converted to a new `struct` and placed in
|
||||
/// the parent scope. The created type name will be {EnumName}{ConstructorName}.
|
||||
/// To name generated types with only their constructor name, use `flat`
|
||||
/// attribute: `#[ast(flat)]`.
|
||||
///
|
||||
/// The target enum will
|
||||
#[proc_macro_attribute]
|
||||
pub fn to_variant_types
|
||||
( attrs: proc_macro::TokenStream
|
||||
@ -245,6 +249,70 @@ pub fn to_variant_types
|
||||
#(#structs)*
|
||||
#(#variant_froms)*
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
/// Creates a HasRepr and HasSpan implementations for a given enum type.
|
||||
///
|
||||
/// Given type may only consist of single-elem tuple-like variants.
|
||||
/// The implementation uses underlying HasRepr and HasSpan implementations for
|
||||
/// stored values.
|
||||
#[proc_macro_derive(HasRepr)]
|
||||
pub fn derive_has_span
|
||||
(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let decl = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
let ret = match decl.data {
|
||||
syn::Data::Enum(ref e) => repr::derive_for_enum(&decl, &e),
|
||||
_ => quote! {},
|
||||
};
|
||||
proc_macro::TokenStream::from(ret)
|
||||
}
|
||||
|
||||
/// Same as `make_repr_span` but provides only `HasSpan` implementation.
|
||||
#[proc_macro]
|
||||
pub fn make_span(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let maker = syn::parse::<ReprDescription>(input).unwrap();
|
||||
maker.make_span().into()
|
||||
}
|
||||
|
||||
/// Generates `HasRepr` and `HasSpan` instances that are just sum of their
|
||||
/// parts.
|
||||
///
|
||||
/// Takes 1+ parameters:
|
||||
/// * first goes the typename for which implementations are generated (can take
|
||||
/// type parameters, as long as they implement `HasRepr` and `HasSpan`)
|
||||
/// * then arbitrary number (0 or more) of expressions, that shall yield values
|
||||
/// implementing `HasRepr` and `HasSpan`. The `self` can be used in th
|
||||
/// expressions.
|
||||
///
|
||||
/// For example, for invocation:
|
||||
/// ```ignore
|
||||
/// make_repr_span!(SegmentExpr<T>, EXPR_QUOTE, self.value, EXPR_QUOTE);
|
||||
/// ```
|
||||
/// the following output is produced:
|
||||
/// ```ignore
|
||||
/// impl<T: HasRepr> HasRepr for SegmentExpr<T> {
|
||||
/// fn write_repr(&self, target: &mut String) {
|
||||
/// EXPR_QUOTE.write_repr(target);
|
||||
/// self.value.write_repr(target);
|
||||
/// EXPR_QUOTE.write_repr(target);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl<T: HasSpan> HasSpan for SegmentExpr<T> {
|
||||
/// fn span(&self) -> usize {
|
||||
/// 0 + EXPR_QUOTE.span() + self.value.span() + EXPR_QUOTE.span()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn make_repr_span(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let maker = syn::parse::<ReprDescription>(input).unwrap();
|
||||
maker.make_repr_span().into()
|
||||
}
|
||||
|
||||
/// Generates `HasRepr` and `HasSpan` implementations that panic when used.
|
||||
#[proc_macro]
|
||||
pub fn not_supported_repr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
crate::repr::not_supported(input)
|
||||
}
|
||||
|
143
common/rust/ast/macros/src/repr.rs
Normal file
143
common/rust/ast/macros/src/repr.rs
Normal file
@ -0,0 +1,143 @@
|
||||
use prelude::*;
|
||||
|
||||
use macro_utils::{path_segment_generic_args};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::punctuated::Punctuated;use syn::Expr;
|
||||
use syn::Token;
|
||||
|
||||
/// Generates `HasRepr` and `HasSpan` that just panic when called.
|
||||
pub fn not_supported
|
||||
(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let target = syn::parse::<syn::PathSegment>(input).unwrap();
|
||||
let ty_args = path_segment_generic_args(&target);
|
||||
let ret = quote!{
|
||||
// Sample expansion for: Import<T>
|
||||
//
|
||||
// impl<T> HasSpan for Import<T> {
|
||||
// fn span(&self) -> usize {
|
||||
// panic!("HasSpan is not supported for Spaceless AST!")
|
||||
// }
|
||||
// }
|
||||
// impl<T> HasRepr for Import<T> {
|
||||
// fn write_repr(&self, target:&mut String) {
|
||||
// panic!("HasRepr not supported for Spaceless AST!")
|
||||
// }
|
||||
// }
|
||||
impl<#(#ty_args),*> HasSpan for #target {
|
||||
fn span(&self) -> usize {
|
||||
panic!("HasSpan not supported for Spaceless AST!")
|
||||
}
|
||||
}
|
||||
impl<#(#ty_args),*> HasRepr for #target {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
panic!("HasRepr not supported for Spaceless AST!")
|
||||
}
|
||||
}
|
||||
};
|
||||
ret.into()
|
||||
}
|
||||
|
||||
/// Inner logic for `derive_has_span`.
|
||||
pub fn derive_for_enum
|
||||
(decl:&syn::DeriveInput, data:&syn::DataEnum)
|
||||
-> TokenStream {
|
||||
let ident = &decl.ident;
|
||||
let params = decl.generics.params.iter().collect_vec();
|
||||
let span_arms = data.variants.iter().map(|v| {
|
||||
let con_ident = &v.ident;
|
||||
quote!( #ident::#con_ident (elem) => elem.span() )
|
||||
});
|
||||
let repr_arms = data.variants.iter().map(|v| {
|
||||
let con_ident = &v.ident;
|
||||
quote!( #ident::#con_ident (elem) => elem.write_repr(target) )
|
||||
});
|
||||
let ret = quote! {
|
||||
impl<#(#params:HasSpan),*> HasSpan for #ident<#(#params),*> {
|
||||
fn span(&self) -> usize {
|
||||
match self {
|
||||
#(#span_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<#(#params:HasRepr),*> HasRepr for #ident<#(#params),*> {
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
match self {
|
||||
#(#repr_arms),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
ret
|
||||
}
|
||||
|
||||
/// Structure representing input to macros like `make_repr_span!`.
|
||||
///
|
||||
/// Basically it consists of a typename (with optional generic arguments) and
|
||||
/// sequence of expressions that yield values we use to obtain sub-repr or
|
||||
/// sub-spans.
|
||||
pub struct ReprDescription {
|
||||
pub ty :syn::PathSegment,
|
||||
pub ty_args:Vec<syn::GenericArgument>,
|
||||
pub exprs :Vec<syn::Expr>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for ReprDescription {
|
||||
/// Parser user-provided input to macro into out structure.
|
||||
///
|
||||
/// First should go a type for which implementation is to be provided,
|
||||
/// then arbitrary sequence of expressions.
|
||||
/// Panics on invalid input, which is actually fair for a macro code.
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let ty:syn::PathSegment = input.parse()?;
|
||||
input.parse::<Option<syn::token::Comma>>()?;
|
||||
let exprs = Punctuated::<Expr,Token![,]>::parse_terminated(input)?;
|
||||
let exprs = exprs.iter().cloned().collect::<Vec<_>>();
|
||||
let ty_args = path_segment_generic_args(&ty);
|
||||
let ty_args = ty_args.into_iter().cloned().collect(); // get rid of &
|
||||
Ok(ReprDescription {ty,ty_args,exprs})
|
||||
}
|
||||
}
|
||||
|
||||
impl ReprDescription {
|
||||
/// Fills a trait implementation template with given methods.
|
||||
pub fn make_impl
|
||||
(&self, trait_name:&str, methods:&TokenStream) -> TokenStream {
|
||||
let trait_name = syn::parse_str::<syn::TypePath>(trait_name).unwrap();
|
||||
let ty = &self.ty;
|
||||
let ty_args = &self.ty_args;
|
||||
quote! {
|
||||
impl<#(#ty_args:#trait_name),*> #trait_name for #ty {
|
||||
#methods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates `HasRepr` instances using user-provided input.
|
||||
pub fn make_repr(&self) -> TokenStream {
|
||||
let exprs = &self.exprs;
|
||||
self.make_impl("HasRepr", "e!{
|
||||
fn write_repr(&self, target:&mut String) {
|
||||
#(#exprs.write_repr(target);)*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates `HasSpan` instances using user-provided input.
|
||||
pub fn make_span(&self) -> TokenStream {
|
||||
let exprs = &self.exprs;
|
||||
self.make_impl("HasSpan", "e!{
|
||||
fn span(&self) -> usize {
|
||||
0 #(+ #exprs.span())*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Generates `HasRepr` and `HasSpan` instances using user-provided input.
|
||||
pub fn make_repr_span(&self) -> TokenStream {
|
||||
let mut ret = self.make_repr();
|
||||
ret.extend(self.make_span());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,65 @@
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! A number of helper functions meant to be used in the procedural macros
|
||||
//! definitions.
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use quote::quote;
|
||||
use syn;
|
||||
use syn::visit::{self, Visit};
|
||||
use syn::visit::Visit;
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::TokenTree;
|
||||
|
||||
// =========================
|
||||
// === Token Stream Utils ===
|
||||
// =========================
|
||||
|
||||
/// Maps all the tokens in the stream using a given function.
|
||||
pub fn map_tokens<F:Fn(TokenTree) -> TokenTree>
|
||||
(input:TokenStream, f:F) -> TokenStream {
|
||||
let ret_iter = input.into_iter().map(f);
|
||||
TokenStream::from_iter(ret_iter)
|
||||
}
|
||||
|
||||
/// Rewrites stream replacing each token with a sequence of tokens returned by
|
||||
/// the given function. The groups (e.g. token tree within braces) are unpacked,
|
||||
/// rewritten and repacked into groups -- the function is applied recursively.
|
||||
pub fn rewrite_stream
|
||||
<F:Fn(TokenTree) -> TokenStream + Copy>
|
||||
(input:TokenStream, f:F) -> TokenStream {
|
||||
let mut ret = TokenStream::new();
|
||||
for token in input.into_iter() {
|
||||
match token {
|
||||
proc_macro2::TokenTree::Group(group) => {
|
||||
let delim = group.delimiter();
|
||||
let span = group.span();
|
||||
let rewritten = rewrite_stream(group.stream(), f);
|
||||
let mut new_group = proc_macro2::Group::new(delim,rewritten);
|
||||
new_group.set_span(span);
|
||||
let new_group = vec![TokenTree::from(new_group)];
|
||||
ret.extend(new_group.into_iter())
|
||||
}
|
||||
_ => ret.extend(f(token)),
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Token Utils ===
|
||||
// ===================
|
||||
|
||||
/// Is the given token an identifier matching to a given string?
|
||||
pub fn matching_ident(token:&TokenTree, name:&str) -> bool {
|
||||
match token {
|
||||
TokenTree::Ident(ident) => ident.to_string() == name,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============
|
||||
@ -16,6 +72,7 @@ pub fn repr<T: quote::ToTokens>(t:&T) -> String {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================
|
||||
// === Field Utils ===
|
||||
// ===================
|
||||
@ -40,6 +97,7 @@ pub fn field_ident_token(field:&syn::Field, index:syn::Index) -> TokenStream {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Type Path Utils ===
|
||||
// =======================
|
||||
@ -80,16 +138,19 @@ pub fn last_type_arg(ty_path:&syn::TypePath) -> Option<&syn::GenericArgument> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === Collect Types ===
|
||||
// =====================
|
||||
|
||||
/// Visitor that accumulates all visited `syn::TypePath`.
|
||||
pub struct TypeGatherer<'ast> {
|
||||
/// Observed types accumulator.
|
||||
pub types: Vec<&'ast syn::TypePath>
|
||||
}
|
||||
|
||||
impl TypeGatherer<'_> {
|
||||
/// Create a new visitor value.
|
||||
pub fn new() -> Self {
|
||||
let types = default();
|
||||
Self { types }
|
||||
@ -99,7 +160,7 @@ impl TypeGatherer<'_> {
|
||||
impl<'ast> Visit<'ast> for TypeGatherer<'ast> {
|
||||
fn visit_type_path(&mut self, node:&'ast syn::TypePath) {
|
||||
self.types.push(node);
|
||||
visit::visit_type_path(self, node);
|
||||
syn::visit::visit_type_path(self, node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +177,7 @@ pub fn gather_all_type_reprs(node:&syn::Type) -> Vec<String> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Type Dependency ===
|
||||
// =======================
|
||||
@ -145,6 +207,7 @@ pub fn variant_depends_on
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
|
@ -1,3 +1,5 @@
|
||||
#![feature(generators, generator_trait)]
|
||||
|
||||
use prelude::*;
|
||||
|
||||
use ast::*;
|
||||
@ -41,6 +43,16 @@ fn assert_opr<StringLike:Into<String>>(ast:&Ast, name:StringLike) {
|
||||
assert_eq!(*actual,expected);
|
||||
}
|
||||
|
||||
/// Checks if all nodes in subtree have declared spans equal to
|
||||
/// spans we calculate.
|
||||
fn validate_spans(ast:&Ast) {
|
||||
for node in ast.iter_recursive() {
|
||||
let calculated = node.shape().span();
|
||||
let declared = node.wrapped.wrapped.span;
|
||||
assert_eq!(calculated, declared
|
||||
, "`{}` part of `{}`", node.repr(), ast.repr());
|
||||
}
|
||||
}
|
||||
|
||||
// ===================
|
||||
// === ExpectTuple ===
|
||||
@ -112,7 +124,12 @@ impl Fixture {
|
||||
|
||||
/// Runs parser on given input, panics on any error.
|
||||
fn parse(&mut self, program:&str) -> Ast {
|
||||
self.0.parse(program.into()).unwrap()
|
||||
println!("parsing {}", program);
|
||||
let ast = self.0.parse(program.into()).unwrap();
|
||||
assert_eq!(ast.shape().span(), program.len());
|
||||
validate_spans(&ast);
|
||||
assert_eq!(ast.repr(), program, "{:?}", ast);
|
||||
ast
|
||||
}
|
||||
|
||||
/// Program is expected to be single line module. The line's AST is
|
||||
@ -121,8 +138,8 @@ impl Fixture {
|
||||
let ast = self.parse(program);
|
||||
let line = expect_single_line(&ast);
|
||||
line.clone()
|
||||
|
||||
}
|
||||
|
||||
/// Program is expected to be single line module. The line's Shape subtype
|
||||
/// is obtained and passed to `tester`.
|
||||
fn test_shape<T,F>(&mut self, program:&str, tester:F)
|
||||
@ -223,6 +240,14 @@ impl Fixture {
|
||||
});
|
||||
}
|
||||
|
||||
fn test_text_fmt_segment<F>(&mut self, program:&str, tester:F)
|
||||
where F: FnOnce(&SegmentFmt<Ast>) -> () {
|
||||
self.test_shape(program,|shape:&TextLineFmt<Ast>| {
|
||||
let (segment,) = (&shape.text).expect_tuple();
|
||||
tester(segment)
|
||||
});
|
||||
}
|
||||
|
||||
fn deserialize_text_line_fmt(&mut self) {
|
||||
use SegmentFmt::SegmentExpr;
|
||||
|
||||
@ -244,8 +269,7 @@ impl Fixture {
|
||||
|
||||
// expression empty
|
||||
let expr_fmt = r#"'``'"#;
|
||||
self.test_shape(expr_fmt,|shape:&TextLineFmt<Ast>| {
|
||||
let (segment,) = (&shape.text).expect_tuple();
|
||||
self.test_text_fmt_segment(expr_fmt,|segment| {
|
||||
match segment {
|
||||
SegmentExpr(expr) => assert_eq!(expr.value,None),
|
||||
_ => panic!("wrong segment type received"),
|
||||
@ -254,8 +278,7 @@ impl Fixture {
|
||||
|
||||
// expression non-empty
|
||||
let expr_fmt = r#"'`foo`'"#;
|
||||
self.test_shape(expr_fmt,|shape:&TextLineFmt<Ast>| {
|
||||
let (segment,) = (&shape.text).expect_tuple();
|
||||
self.test_text_fmt_segment(expr_fmt,|segment| {
|
||||
match segment {
|
||||
SegmentExpr(expr) =>
|
||||
assert_var(expr.value.as_ref().unwrap(),"foo"),
|
||||
@ -263,21 +286,19 @@ impl Fixture {
|
||||
}
|
||||
});
|
||||
|
||||
let expr_fmt = r#"'\n\u0394\U0001f34c'"#;
|
||||
self.test_shape(expr_fmt,|shape:&TextLineFmt<Ast>| {
|
||||
let segments: (_,_,_) = (&shape.text).expect_tuple();
|
||||
|
||||
let expected = Escape::Character{c:'n'};
|
||||
assert_eq!(*segments.0,expected.into());
|
||||
|
||||
let expected = Escape::Unicode16{digits: "0394".into()};
|
||||
assert_eq!(*segments.1,expected.into());
|
||||
|
||||
// TODO We don't test Unicode21 as it is not yet supported by
|
||||
// parser.
|
||||
|
||||
let expected = Escape::Unicode32{digits: "0001f34c".into()};
|
||||
assert_eq!(*segments.2,expected.into());
|
||||
self.test_text_fmt_segment(r#"'\n'"#,|segment| {
|
||||
let expected = EscapeCharacter{c:'n'};
|
||||
assert_eq!(*segment,expected.into());
|
||||
});
|
||||
self.test_text_fmt_segment(r#"'\u0394'"#,|segment| {
|
||||
let expected = EscapeUnicode16{digits: "0394".into()};
|
||||
assert_eq!(*segment,expected.into());
|
||||
});
|
||||
// TODO [MWU] We don't test Unicode21 as it is not yet supported by the
|
||||
// parser.
|
||||
self.test_text_fmt_segment(r#"'\U0001f34c'"#,|segment| {
|
||||
let expected = EscapeUnicode32{digits: "0001f34c".into()};
|
||||
assert_eq!(*segment,expected.into());
|
||||
});
|
||||
}
|
||||
|
||||
@ -401,6 +422,7 @@ impl Fixture {
|
||||
[ "foo -> bar"
|
||||
, "()"
|
||||
, "(foo -> bar)"
|
||||
, "a b c -> bar"
|
||||
, "type Maybe a\n Just val:a"
|
||||
, "foreign Python3\n bar"
|
||||
, "if foo > 8 then 10 else 9"
|
||||
@ -457,6 +479,7 @@ impl Fixture {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A single entry point for all the tests here using external parser.
|
||||
///
|
||||
/// Setting up the parser is costly, so we run all tests as a single batch.
|
||||
|
@ -1,5 +1,9 @@
|
||||
#![feature(generators, generator_trait)]
|
||||
#![feature(type_ascription)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Helper code meant to be used by the code generated through usage of macros
|
||||
//! from `shapely-macros` crate.
|
||||
|
||||
pub use shapely_macros::*;
|
||||
|
||||
@ -27,6 +31,8 @@ where G: Generator<Return = ()> + Unpin {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================
|
||||
// === EmptyIterator ===
|
||||
// =====================
|
||||
@ -48,6 +54,7 @@ impl<T> Iterator for EmptyIterator<T> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============
|
||||
// === Tests ===
|
||||
// =============
|
||||
@ -78,4 +85,4 @@ mod tests {
|
||||
let collected_result: Vec<_> = generator_iter.collect();
|
||||
assert_eq!(collected_result, expected_numbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ where T : IntoIterator,
|
||||
t.into_iter().collect()
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =====================================
|
||||
// === Struct with single type param ===
|
||||
// =====================================
|
||||
@ -57,6 +59,8 @@ fn derive_iterator_single_t() {
|
||||
assert_eq!(sum, pair.0 + pair.1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ===================================
|
||||
// === Struct with two type params ===
|
||||
// ===================================
|
||||
@ -71,6 +75,8 @@ fn two_params() {
|
||||
assert_eq!(to_vector(pair.iter().copied()), vec![10]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ======================================
|
||||
// === Struct without any type params ===
|
||||
// ======================================
|
||||
@ -84,6 +90,8 @@ fn no_params() {
|
||||
// We just make sure that it does not cause compilation error.
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ========================
|
||||
// === Enumeration Type ===
|
||||
// ========================
|
||||
@ -126,6 +134,8 @@ fn enum_iter3() {
|
||||
assert!(v_iter.next().is_none());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =======================
|
||||
// === Dependent Types ===
|
||||
// =======================
|
||||
@ -135,6 +145,7 @@ fn enum_iter3() {
|
||||
pub struct DependentTest<U, T> {
|
||||
a:T,
|
||||
b:(T,U,PairUV<U, T>),
|
||||
// is never used, as it doesn't depend on `T` (last param)
|
||||
#[allow(dead_code)]
|
||||
c:PairTT<U>,
|
||||
d:(i32, Option<Vec<T>>),
|
||||
|
Loading…
Reference in New Issue
Block a user