Skip to content

Commit

Permalink
feat: Use #[IDENTIFIER(..)] as the attribute syntax
Browse files Browse the repository at this point in the history
Changes the syntax of attributes by breaking them out of comments and
into their own syntactical element.

With the attribute in comment syntax it wasn't clear that attributes had
an effect on the code. The same syntax as Rust was chosen mostly out of
familiarity, if a compelling argument for another syntax is made it
could be changed (and the formatter could perhaps help in migration as
that should be simple).

Closes #515
  • Loading branch information
Marwes committed May 13, 2018
1 parent 4ffb1ec commit 0300590
Show file tree
Hide file tree
Showing 42 changed files with 632 additions and 346 deletions.
41 changes: 15 additions & 26 deletions base/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::ops::{Deref, DerefMut};
use pos::{self, BytePos, HasSpan, Span, Spanned};
use symbol::Symbol;
use types::{self, Alias, AliasData, ArcType, ArgType, Generic, Type, TypeEnv};
use metadata::{Comment, Metadata};
use ordered_float::NotNaN;

pub trait DisplayEnv {
Expand Down Expand Up @@ -100,6 +101,10 @@ impl<Id> HasSpan for AstType<Id> {
}
}

pub trait Commented {
fn comment(&self) -> Option<&Comment>;
}

impl<Id> Commented for AstType<Id> {
fn comment(&self) -> Option<&Comment> {
self._typ.0.as_ref()
Expand Down Expand Up @@ -147,22 +152,6 @@ impl<Id> AstType<Id> {
}
}

pub trait Commented {
fn comment(&self) -> Option<&Comment>;
}

#[derive(Clone, Copy, Eq, PartialEq, Debug)]
pub enum CommentType {
Block,
Line,
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Comment {
pub typ: CommentType,
pub content: String,
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct TypedIdent<Id = Symbol> {
pub typ: ArcType<Id>,
Expand Down Expand Up @@ -260,7 +249,7 @@ pub type SpannedAstType<Id> = Spanned<Type<Id, AstType<Id>>, BytePos>;

#[derive(Clone, PartialEq, Debug)]
pub struct ExprField<Id, E> {
pub comment: Option<Comment>,
pub metadata: Metadata,
pub name: Spanned<Id, BytePos>,
pub value: Option<E>,
}
Expand Down Expand Up @@ -339,7 +328,7 @@ pub enum Expr<Id> {

#[derive(Clone, PartialEq, Debug)]
pub struct TypeBinding<Id> {
pub comment: Option<Comment>,
pub metadata: Metadata,
pub name: Spanned<Id, BytePos>,
pub alias: SpannedAlias<Id>,
pub finalized_alias: Option<Alias<Id, ArcType<Id>>>,
Expand Down Expand Up @@ -375,7 +364,7 @@ impl<Id> Argument<Id> {

#[derive(Clone, PartialEq, Debug)]
pub struct ValueBinding<Id> {
pub comment: Option<Comment>,
pub metadata: Metadata,
pub name: SpannedPattern<Id>,
pub typ: Option<AstType<Id>>,
pub resolved_type: ArcType<Id>,
Expand Down Expand Up @@ -639,11 +628,11 @@ pub fn walk_mut_ast_type<'a, V: ?Sized + MutVisitor<'a>>(
}
v.visit_ast_type(&mut rest._typ.1);
}
Type::Ident(_) => (),
Type::Variable(_) => (),
Type::Generic(_) => (),
Type::Alias(_) => (),
Type::Skolem(_) => (),
Type::Ident(_)
| Type::Variable(_)
| Type::Generic(_)
| Type::Alias(_)
| Type::Skolem(_) => (),
}
}

Expand All @@ -654,8 +643,8 @@ pub trait Visitor<'a> {
walk_expr(self, e);
}

fn visit_pattern(&mut self, e: &'a SpannedPattern<Self::Ident>) {
walk_pattern(self, &e.value);
fn visit_pattern(&mut self, p: &'a SpannedPattern<Self::Ident>) {
walk_pattern(self, &p.value);
}

fn visit_typ(&mut self, _: &'a ArcType<Self::Ident>) {}
Expand Down
69 changes: 35 additions & 34 deletions base/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,61 +18,62 @@ impl MetadataEnv for () {
}
}

#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub enum CommentType {
Block,
Line,
}

#[derive(Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Comment {
pub typ: CommentType,
pub content: String,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Attribute {
pub name: String,
pub arguments: Option<String>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
#[cfg_attr(feature = "serde_derive", derive(Deserialize, Serialize))]
pub struct Metadata {
pub comment: Option<String>,
pub comment: Option<Comment>,
pub attributes: Vec<Attribute>,
pub module: BTreeMap<String, Metadata>,
}

impl Metadata {
pub fn has_data(&self) -> bool {
self.comment.is_some() || !self.module.is_empty()
self.comment.is_some() || !self.module.is_empty() || !self.attributes.is_empty()
}

pub fn merge(mut self, other: Metadata) -> Metadata {
self.merge_with(other);
self
}

pub fn merge_with(&mut self, other: Metadata) {
if self.comment.is_none() {
self.comment = other.comment;
}
if self.module.is_empty() {
self.module = other.module;
}
self
self.attributes.extend(other.attributes);
}

pub fn get_attribute(&self, attribute: &str) -> Option<&str> {
pub fn get_attribute(&self, name: &str) -> Option<&str> {
self.attributes()
.find(|&(name, _)| name == attribute)
.map(|t| t.1.unwrap_or(""))
}

pub fn attributes(&self) -> Attributes {
attributes(self.comment.as_ref().map_or("", |s| s))
}
}

pub struct Attributes<'a> {
comment: ::std::str::Lines<'a>,
}

impl<'a> Iterator for Attributes<'a> {
type Item = (&'a str, Option<&'a str>);

fn next(&mut self) -> Option<Self::Item> {
while let Some(line) = self.comment.next() {
if line.starts_with('@') {
let mut split = line[1..].splitn(2, ' ');
if let Some(x) = split.next().map(|key| (key, split.next())) {
return Some(x);
}
}
}
None
.find(|attribute| attribute.name == name)
.map(|t| t.arguments.as_ref().map_or("", |s| s))
}
}

pub fn attributes(comment: &str) -> Attributes {
Attributes {
comment: comment.lines(),
pub fn attributes(&self) -> impl Iterator<Item = &Attribute> {
self.attributes.iter()
}
}
3 changes: 2 additions & 1 deletion base/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use pretty::{Arena, DocAllocator, DocBuilder};

use smallvec::SmallVec;

use ast::{Comment, Commented, EmptyEnv, IdentEnv};
use ast::{Commented, EmptyEnv, IdentEnv};
use fnv::FnvMap;
use kind::{ArcKind, Kind, KindEnv};
use merge::merge;
use metadata::Comment;
use pos::{BytePos, HasSpan, Span};
use source::Source;
use symbol::{Symbol, SymbolRef};
Expand Down
3 changes: 2 additions & 1 deletion base/src/types/pretty_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use std::ops::Deref;

use pretty::{Arena, DocAllocator, DocBuilder};

use ast::{is_operator_char, Comment, CommentType, Commented};
use ast::{is_operator_char, Commented};
use metadata::{Comment, CommentType};
use pos::{BytePos, HasSpan, Span};
use source::Source;

Expand Down
34 changes: 19 additions & 15 deletions book/src/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let add2 x = x + 2
add1 // Looking up the metadata of this variable yields the documentation of `add1`
// It can't be statically determined which branch the `if` takes (since constant folding do not
// take place). Thus `addN` do not get any metadta from either `add1` or `add2`
// take place). Thus `addN` do not get any metadata from either `add1` or `add2`
let addN = if True then add1 else add2
addN
```
Expand All @@ -24,40 +24,44 @@ addN

In addtion to documentation comments gluon also has a special notion of attributes that get propagated in the same manner. These are specified using the following syntax.

```f#
/// @<NAME> <VALUE?>
```
Attribute : #[ AttributeContents ]
AttributeContents :
#[ IDENTIFIER ]
| #[ IDENTIFIER ( TOKENS* ) ]
```

### @infix
### #[infix(..)]

```f#
/// @infix (left|right) <NON-NEGATIVE INTEGER>
#[infix(<left|right>, <NON-NEGATIVE INTEGER>)]
```

The `@infix` attribute is used to specified the fixity and precedence of infix operators. This lets us specify that multiplication binds tighter that addition.
The `#[infix]` attribute is used to specified the fixity and precedence of infix operators. This lets us specify that multiplication binds tighter that addition.

```f#
/// @infix left 6
let (+) ?num : [num a] -> a -> a -> a = num.(+)
/// @infix left 7
let (*) ?num : [num a] -> a -> a -> a = num.(*)
#[infix(left, 6)]
let (+) ?num : [Num a] -> a -> a -> a = num.(+)
#[infix(left, 7)]
let (*) ?num : [Num a] -> a -> a -> a = num.(*)
```


### @implicit
### #[implicit]

```f#
/// @implicit
#[implicit]
```

The `@implicit` attribute is used to mark value bindings or type bindings as usable for implicit resolution. If specified on a value binding then only that specific binding can be used on implicit resolution. If specified on a type binding then all bindings that has that type can be used in implicit resolution.
The `#[implicit]` attribute is used to mark value bindings or type bindings as usable for implicit resolution. If specified on a value binding then only that specific binding can be used on implicit resolution. If specified on a type binding then all bindings that has that type can be used in implicit resolution.

```
// Can be used as an implicit argument
/// @implicit
#[implicit]
let binding : MyType = ..
/// @implicit
#[implicit]
type Eq a = { (==) : a -> a -> Bool }
// Can be used as an implicit argument
Expand Down
9 changes: 5 additions & 4 deletions book/src/syntax-and-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,14 +461,14 @@ Sometimes, there is a need to overload a name with multiple differing implementa

This different looking argument is an implicit argument which means that you do not need to pass a value for this argument, instead, the compiler will try to find a value with a type that matches the type signature. So if you were to call `1 == 2` the compiler will see that the type variable `a` has been unified to `Int`. Then when the implicit argument is resolved it will look for a value with the type `Eq Int`.

Since searching all possible bindings currently in scope would introduce to many ambiguity errors the compiler does not search all bindings when trying to determine an implicit argument. Instead, whether a binding is considered for implicit resolution is controlled by the `@implicit` attribute. When marking a `let` binding as `@implicit` and this binding is in scope it will be considered as a candidate for all implicit arguments. The `@implicit` attribute can also be set on a `type` binding in which case it applied to all `let` bindings which has the type declared by the `type` binding.
Since searching all possible bindings currently in scope would introduce to many ambiguity errors the compiler does not search all bindings when trying to determine an implicit argument. Instead, whether a binding is considered for implicit resolution is controlled by the `#[implicit]` attribute. When marking a `let` binding as `#[implicit]` and this binding is in scope it will be considered as a candidate for all implicit arguments. The `#[implicit]` attribute can also be set on a `type` binding in which case it applied to all `let` bindings which has the type declared by the `type` binding.

```f#,rust
/// @implicit
#[implicit]
type Test = | Test ()
let f y: [a] -> a -> a = y
let i = Test ()
// `i` gets selected as the implicit argument since `@implicit` is marked on the type and `i : Test`
// `i` gets selected as the implicit argument since `#[implicit]` is marked on the type and `i : Test`
()
```

Expand All @@ -494,7 +494,8 @@ If you only use implicit functions as explained above then it might just seem li
```f#,rust
let list @ { List } = import! std.list
// Make a custom equality function which returns true regardless of the elements of the list
let { (==) = (===) } = list.eq ?{ (==) = \x y -> True }
#[infix(left, 4)]
let (===) = (list.eq ?{ (==) = \x y -> True }).(==)
Cons 1 (Cons 2 Nil) === Cons 3 (Cons 4 Nil)
```

Expand Down
Loading

0 comments on commit 0300590

Please sign in to comment.