Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,20 @@ References

* [The Scala Language Specification](http://www.scala-lang.org/docu/files/ScalaReference.pdf)
* [Scala Syntax Summary](https://www.scala-lang.org/files/archive/spec/2.11/13-syntax-summary.html)

Development
-----------

First, install the project's dependencies:

```sh
npm install
```

Add a test case to `./corpus`, make the required changes to `grammar.js`,
regenerate and recompile the parser, and run the tests:

```sh
npm run build
npm test
```
70 changes: 65 additions & 5 deletions corpus/definitions.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
================================
Package
================================

package a.b
package c {
object A
}

---

(compilation_unit
(package_clause (package_identifier (identifier) (identifier)))
(package_clause (package_identifier (identifier))
(template_body
(object_definition (identifier)))))

================================
Package object
================================

package object d extends A {
val hello: String = "there"
}

---

(compilation_unit
(package_object
(identifier)
(extends_clause (type_identifier))
(template_body
(val_definition (identifier) (type_identifier) (string)))))

================================
Imports
================================

import PartialFunction.condOpt
import a.b, c.e
import reflect.io.{Directory, File, Path}
import tools.nsc.classpath._
import lang.System.{lineSeparator => EOL}

---

Expand All @@ -14,10 +47,33 @@ import lang.System.{lineSeparator => EOL}
(stable_identifier (identifier) (identifier)))
(import_declaration
(stable_identifier (identifier) (identifier))
(import_selectors (identifier) (identifier) (identifier)))
(stable_identifier (identifier) (identifier)))
(import_declaration
(stable_identifier (identifier) (identifier))
(import_selectors (identifier) (identifier) (identifier))))

================================
Imports: Wildcard
================================

import tools.nsc.classpath._

---

(compilation_unit
(import_declaration
(stable_identifier (stable_identifier (identifier) (identifier)) (identifier))
(wildcard))
(wildcard)))

================================
Imports: Rename
================================

import lang.System.{lineSeparator => EOL}

---

(compilation_unit
(import_declaration
(stable_identifier (identifier) (identifier))
(import_selectors (renamed_identifier (identifier) (identifier)))))
Expand All @@ -33,12 +89,16 @@ object O1 {
case object O2 {
}

object O3 extends A {
}

---

(compilation_unit
(comment)
(object_definition (identifier) (template_body))
(object_definition (identifier) (template_body)))
(object_definition (identifier) (template_body))
(object_definition (identifier) (extends_clause (type_identifier)) (template_body)))

=======================================
Class definitions
Expand Down
80 changes: 80 additions & 0 deletions corpus/types.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,83 @@ type A = B Foo C
(type_definition
(type_identifier)
(infix_type (type_identifier) (identifier) (type_identifier))))


==================================
Variant Types
==================================

class Function1[-T1, +R]

---

(compilation_unit
(class_definition
(identifier)
(type_parameters
(contravariant_type_parameter (identifier))
(covariant_type_parameter (identifier)))))


==================================
Upper bound
==================================

class A[B <: C]

---

(compilation_unit
(class_definition
(identifier)
(type_parameters
(identifier)
(upper_bound (type_identifier)))))

==================================
Lower bound
==================================

class A[B >: C]

---

(compilation_unit
(class_definition
(identifier)
(type_parameters
(identifier)
(lower_bound (type_identifier)))))

==================================
View bound
==================================


class A[B <% C <% D]

---

(compilation_unit
(class_definition
(identifier)
(type_parameters
(identifier)
(view_bound (type_identifier))
(view_bound (type_identifier)))))

==================================
Context bound
==================================

class A[B : C : D]

---

(compilation_unit
(class_definition
(identifier)
(type_parameters
(identifier)
(context_bound (type_identifier))
(context_bound (type_identifier)))))
9 changes: 9 additions & 0 deletions examples/Packages.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package a.b
package c {
object A
}
package c {
package object d {
val hello: String = "there"
}
}
1 change: 1 addition & 0 deletions examples/Variance.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
class Function1[-T1, +R]
71 changes: 65 additions & 6 deletions grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ module.exports = grammar({

_definition: $ => choice(
$.package_clause,
$.package_object,
$.class_definition,
$.import_declaration,
$.object_definition,
Expand All @@ -54,11 +55,30 @@ module.exports = grammar({

package_clause: $ => seq(
'package',
choice($.identifier, $.stable_identifier)
$.package_identifier,
// This is slightly more permissive than the EBNF in that it allows any
// kind of delcaration inside of the package blocks. As we're more
// concerned with the structure rather than the validity of the program
// we'll allow it.
optional($.template_body)
),

package_identifier: $ => sep1(
'.', $.identifier
),

package_object: $ => seq(
'package',
'object',
$._object_definition
),

import_declaration: $ => seq(
'import',
sep1(',', $._import_expression)
),

_import_expression: $ => seq(
choice($.stable_identifier, $.identifier),
optional(seq(
'.',
Expand Down Expand Up @@ -87,8 +107,13 @@ module.exports = grammar({
object_definition: $ => seq(
optional('case'),
'object',
$._object_definition
),

_object_definition: $ => seq(
$.identifier,
$.template_body
optional($.extends_clause),
optional($.template_body),
),

class_definition: $ => seq(
Expand All @@ -110,17 +135,51 @@ module.exports = grammar({
$.template_body
),

// The EBNF makes a distinction between function type parameters and other
// type parameters as you can't specify variance on function type
// parameters. This isn't important to the structure of the AST so we don't
// make that distinction.
type_parameters: $ => seq(
'[',
commaSep1($._type_parameter),
commaSep1($._variant_type_parameter),
']'
),

_type_parameter: $ => choice(
'_',
$.identifier
_variant_type_parameter: $ => seq(
choice(
$.covariant_type_parameter,
$.contravariant_type_parameter,
$._type_parameter // invariant type parameter
)
),

covariant_type_parameter: $ => seq(
'+',
$._type_parameter
),

contravariant_type_parameter: $ => seq(
'-',
$._type_parameter,
),

_type_parameter: $ => seq(
choice($.wildcard, $.identifier),
optional($.type_parameters),
Copy link
Contributor

@maxbrunsfeld maxbrunsfeld Feb 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the possibility of a type_parameters node within a type parameter. What does it mean when a type parameter has its own parameters? I think we should add a test for this case as well.

optional($.upper_bound),
optional($.lower_bound),
optional(repeat($.view_bound)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repeat rule actually means zero-or-more (there's also a repeat1 built-in, which means one-or-more), so you can remove the optional here.

optional(repeat($.context_bound)),
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I'm seeing how much structure there is to type parameters, I think we should tweak their structure so that there is always a visible syntax node corresponding to each parameter (as opposed to the identifier and upper_bound node appearing as siblings within the type_parameters node).

Maybe something like this:

==================
Type parameters
==================

class A[B, +C <: D, -E : F : G]

---

(compilation_unit
  (class_definition
    (identifier)
    (type_parameters
      (invariant_type_parameter (type_identifier))
      (covariant_type_parameter
        (type_identifier)
        (upper_bound (type_identifier)))
      (contravariant_type_parameter
        (type_identifier)
        (context_bound (type_identifier))
        (context_bound (type_identifier))))))

Does that make sense? We could have a hidden helper rule called _type_parameter that is shared between the three. In addition, we should add the _type_parameter to the inline section of the grammar. That will avoid actually creating that node at runtime for performance reasons.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxbrunsfeld Definitely. I'll create a separate issue for this so we don't forget :)


upper_bound: $ => seq('<:', $._type),

lower_bound: $ => seq('>:', $._type),

view_bound: $ => seq('<%', $._type),

context_bound: $ => seq(':', $._type),

template_body: $ => seq(
'{',
repeat($._definition),
Expand Down
Loading