From 68cc3ea69e9d85e8940ebdb6750934fafdc8a24b Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 4 Feb 2018 14:36:24 -0500 Subject: [PATCH] [ast,parser] Allow types to define supertypes using the type restriction syntax. --- .sentry.yml | 3 ++- spec/syntax/parser_spec.cr | 37 +++++++++++++++++++++++++++++++++++++ src/myst/syntax/ast.cr | 9 +++++---- src/myst/syntax/parser.cr | 30 ++++++++++++++++++++++++++++-- 4 files changed, 72 insertions(+), 7 deletions(-) diff --git a/.sentry.yml b/.sentry.yml index 4f89c3d..ab3cd60 100644 --- a/.sentry.yml +++ b/.sentry.yml @@ -5,7 +5,8 @@ # every time any of the source files changes. display_name: myst specs info: true -build: crystal spec +# `|| echo` makes sure that sentry keeps running even if there the specs fail. +build: crystal spec --no-debug || echo '' run: # nothing to do watch: - ./src/myst/**/*.cr diff --git a/spec/syntax/parser_spec.cr b/spec/syntax/parser_spec.cr index 06e0197..3cb88f3 100644 --- a/spec/syntax/parser_spec.cr +++ b/spec/syntax/parser_spec.cr @@ -1025,6 +1025,43 @@ describe "Parser" do ), TypeDef.new("Thing", body: e(TypeDef.new("Part"))) + # Inheritance + + # Types can inherit from other types using a syntax similar to type restrictions + it_parses %q(deftype Foo : Bar; end), TypeDef.new("Foo", supertype: c("Bar")) + # The supertype can be a namespaced name + it_parses %q(deftype Foo : Bar.Baz; end), TypeDef.new("Foo", supertype: Call.new(c("Bar"), "Baz")) + it_parses %q(deftype Foo : Bar.Baz.Foo; end), TypeDef.new("Foo", supertype: Call.new(Call.new(c("Bar"), "Baz"), "Foo")) + # Type supertype can also be interpolated + it_parses %q(deftype Foo : ; end), TypeDef.new("Foo", supertype: i(Call.new(nil, "get_supertype"))) + # If a colon is given, a type name is required + it_does_not_parse %q(deftype Foo : ; end) + it_does_not_parse %q( + deftype Foo : + end + ) + # The separating colon must be padded with a space to avoid confusion with a Symbol. + it_does_not_parse %q( + deftype Foo:Bar; end + ) + # The supertype name must be given on the same line as the type + it_does_not_parse %q( + deftype Foo : + Bar + end + ) + it_does_not_parse %q( + deftype Foo + : Bar + end + ) + # Type paths must be given with no spaces + it_does_not_parse %q(deftype Foo : Bar . Baz; end) + # Inheritance is only valid on type definitions + it_does_not_parse %q(defmodule Foo : Bar; end) + + + # Type methods it_parses %q( diff --git a/src/myst/syntax/ast.cr b/src/myst/syntax/ast.cr index 55121ed..5f3e87a 100644 --- a/src/myst/syntax/ast.cr +++ b/src/myst/syntax/ast.cr @@ -784,14 +784,15 @@ module Myst # A type definition. TypeDefs are similar to ModuleDefs, but define a data # type that can be instantiated similar to how Literals create primitives. # - # 'deftype' const + # 'deftype' const : const # body # 'end' class TypeDef < Node - property name : String - property body : Node + property name : String + property body : Node + property! supertype : Call | Const | ValueInterpolation | Nil - def initialize(@name, @body=Nop.new) + def initialize(@name, @body=Nop.new, @supertype=nil) end def accept_children(visitor) diff --git a/src/myst/syntax/parser.cr b/src/myst/syntax/parser.cr index 7028a14..0e4a5d0 100644 --- a/src/myst/syntax/parser.cr +++ b/src/myst/syntax/parser.cr @@ -377,17 +377,43 @@ module Myst skip_space name = expect(Token::Type::CONST).value skip_space + # Type definitions can optionally provide a supertype to inherit from. + supertype = + if accept(Token::Type::COLON) + skip_space + parse_type_path + end + skip_space expect_delimiter skip_space_and_newlines if finish = accept(Token::Type::END) - return TypeDef.new(name, Nop.new).at(start.location).at_end(finish.location) + return TypeDef.new(name, Nop.new, supertype: supertype).at(start.location).at_end(finish.location) else push_var_scope body = parse_code_block(Token::Type::END) finish = expect(Token::Type::END) pop_var_scope - return TypeDef.new(name, body).at(start.location).at_end(finish.location) + return TypeDef.new(name, body, supertype: supertype).at(start.location).at_end(finish.location) + end + end + + def parse_type_path + case current_token.type + when Token::Type::CONST + token = current_token + read_token + path = Const.new(token.value).at(token.location) + while accept(Token::Type::POINT) + next_path_part = expect(Token::Type::CONST) + path = Call.new(path, next_path_part.value).at(path.location).at_end(next_path_part.location) + end + + path + when Token::Type::LESS + parse_value_interpolation + else + raise ParseError.new(current_location, "Expected supertype after colon in type definition") end end