diff --git a/skiplang/compiler/src/SkipParseTree.sk b/skiplang/compiler/src/SkipParseTree.sk index edb522452..f09d8e60c 100644 --- a/skiplang/compiler/src/SkipParseTree.sk +++ b/skiplang/compiler/src/SkipParseTree.sk @@ -759,12 +759,14 @@ class CatchClauseTree{ } class ChildClassTree{ + extension: ParseTree.ParseTree, annotations: ParseTree.ParseTree, name: ParseTree.ParseTree, constructor: ParseTree.ParseTree, } extends ParseTree.ParseTree { fun getNamedFields(): List<(String, ParseTree.ParseTree)> { List<(String, ParseTree.ParseTree)>[ + ("extension", this.extension), ("annotations", this.annotations), ("name", this.name), ("constructor", this.constructor), @@ -776,6 +778,7 @@ class ChildClassTree{ } fun getChildren(): mutable Iterator { + yield this.extension; yield this.annotations; yield this.name; yield this.constructor; @@ -785,22 +788,30 @@ class ChildClassTree{ fun transform( codemod: mutable CodeMod, ): (ParseTree.ParseTree, Vector) { + tx_extension = codemod.transform(this.extension); tx_annotations = codemod.transform(this.annotations); tx_name = codemod.transform(this.name); tx_constructor = codemod.transform(this.constructor); ( ChildClassTree{ range => this.range, + extension => tx_extension.i0, annotations => tx_annotations.i0, name => tx_name.i0, constructor => tx_constructor.i0, }, - Vector[tx_annotations.i1, tx_name.i1, tx_constructor.i1].flatten(), + Vector[ + tx_extension.i1, + tx_annotations.i1, + tx_name.i1, + tx_constructor.i1, + ].flatten(), ); } } class ChildrenTree{ + modifiers: ParseTree.ParseTree, childrenKeyword: ParseTree.ParseTree, equal: ParseTree.ParseTree, bar: ParseTree.ParseTree, @@ -808,6 +819,7 @@ class ChildrenTree{ } extends ParseTree.ParseTree { fun getNamedFields(): List<(String, ParseTree.ParseTree)> { List<(String, ParseTree.ParseTree)>[ + ("modifiers", this.modifiers), ("childrenKeyword", this.childrenKeyword), ("equal", this.equal), ("bar", this.bar), @@ -820,6 +832,7 @@ class ChildrenTree{ } fun getChildren(): mutable Iterator { + yield this.modifiers; yield this.childrenKeyword; yield this.equal; yield this.bar; @@ -830,6 +843,7 @@ class ChildrenTree{ fun transform( codemod: mutable CodeMod, ): (ParseTree.ParseTree, Vector) { + tx_modifiers = codemod.transform(this.modifiers); tx_childrenKeyword = codemod.transform(this.childrenKeyword); tx_equal = codemod.transform(this.equal); tx_bar = codemod.transform(this.bar); @@ -837,12 +851,14 @@ class ChildrenTree{ ( ChildrenTree{ range => this.range, + modifiers => tx_modifiers.i0, childrenKeyword => tx_childrenKeyword.i0, equal => tx_equal.i0, bar => tx_bar.i0, childClasses => tx_childClasses.i0, }, Vector[ + tx_modifiers.i1, tx_childrenKeyword.i1, tx_equal.i1, tx_bar.i1, diff --git a/skiplang/compiler/src/SkipParser.sk b/skiplang/compiler/src/SkipParser.sk index ac3aa622a..bb3134722 100644 --- a/skiplang/compiler/src/SkipParser.sk +++ b/skiplang/compiler/src/SkipParser.sk @@ -1543,9 +1543,10 @@ mutable class SkipParser{ | TokenKind.READONLY() | TokenKind.UNTRACKED() -> true - // type constant + // type constant or extension children | TokenKind.NONTYPE_IDENTIFIER() -> - this.peekPredefinedName(PredefinedName.type) + this.peekPredefinedName(PredefinedName.type) || + this.peekPredefinedName(PredefinedName.extension) | _ -> false } } @@ -1570,6 +1571,8 @@ mutable class SkipParser{ | TokenKind.READONLY() | TokenKind.UNTRACKED() -> true + | TokenKind.NONTYPE_IDENTIFIER() -> + this.peekPredefinedName(PredefinedName.extension) | _ -> false } } @@ -1603,9 +1606,14 @@ mutable class SkipParser{ // 9.4 Child classes // child-class: - // annotation-list-opt type-identifier constructor-parameters-opt + // extension-opt annotation-list-opt type-identifier constructor-parameters-opt mutable fun parseChildClass(): ParseTree { start = this.mark(); + extension = if (this.peekPredefinedName(PredefinedName.extension)) { + this.tokenResult() + } else { + this.createEmptyTreeAfter() + }; annotations = this.parseList( parser -> parser.peekAnnotation(), parser -> parser.parseAnnotation(), @@ -1614,6 +1622,7 @@ mutable class SkipParser{ constructor = this.parseChildrenConstructorOpt(); ParseTree.ChildClassTree{ range => this.createRange(start), + extension, annotations, name, constructor, @@ -1621,18 +1630,21 @@ mutable class SkipParser{ } // children-declaration: - // children = |-opt child-class-list + // extension-opt children = |-opt child-class-list // // child-class-list: // child-class // child-class-list | child-class mutable fun parseChildClasses(modifiers: ParseTree): ParseTree { - if (!modifiers.isEmptyList()) { - this.addErrorAtTree( - modifiers, - errorNoModifiersOnChildren, - "Modifiers are not permitted on 'children'", - ); + // Only 'extension' modifier is allowed on 'children' + for (modifier in tokenModifiers(modifiers)) { + if (modifier.getTokenString() != PredefinedName.extension) { + this.addErrorAtTree( + modifier, + errorNoModifiersOnChildren, + "Only 'extension' modifier is permitted on 'children'", + ); + } }; childrenKeyword = this.eatTree(TokenKind.CHILDREN()); equal = this.eatTree(TokenKind.EQUAL()); @@ -1642,6 +1654,7 @@ mutable class SkipParser{ ); ParseTree.ChildrenTree{ range => createRangeOfModifiers(modifiers, childClasses), + modifiers, childrenKeyword, equal, bar, diff --git a/skiplang/compiler/src/convertTree.sk b/skiplang/compiler/src/convertTree.sk index 76bf17d17..deb12a824 100644 --- a/skiplang/compiler/src/convertTree.sk +++ b/skiplang/compiler/src/convertTree.sk @@ -2084,10 +2084,19 @@ class Converter{file: FileCache.InputSource} { fun convertChild(tree: ParseTree): SkipAst.Child { tree match { - | ParseTree.ChildClassTree{range, annotations, name, constructor} -> + | ParseTree.ChildClassTree{ + range, + extension, + annotations, + name, + constructor, + } -> className = this.convertGlobalName(name); SkipAst.Child{ chi_range => this.convertRange(range), + chi_extension => if (extension.isEmpty()) None() else { + Some(this.convertRange(extension.range)) + }, chi_name => className, chi_params => this.convertClassParamsOpt(className, constructor), chi_annotations => this.convertAnnotations(annotations), @@ -2110,8 +2119,22 @@ class Converter{file: FileCache.InputSource} { childrenMembers .map(childrenList -> childrenList match { - | ParseTree.ChildrenTree{childClasses} -> - this.convertList(childClasses, this.convertChild) + | ParseTree.ChildrenTree{modifiers, childClasses} -> + blanketExtension = this.convertModifierName( + modifiers, + PredefinedName.extension, + ); + converted = this.convertList(childClasses, this.convertChild); + blanketExtension match { + | None() -> converted + | Some(extRange) -> + converted.map(chi -> + chi.chi_extension match { + | Some _ -> chi + | None() -> chi with {chi_extension => Some(extRange)} + } + ) + } | _ -> invariant_violation("Unexpected children") } ) diff --git a/skiplang/compiler/src/printer.sk b/skiplang/compiler/src/printer.sk index a9bca5820..34ec85f98 100644 --- a/skiplang/compiler/src/printer.sk +++ b/skiplang/compiler/src/printer.sk @@ -1334,14 +1334,24 @@ fun printTree(ctx: Context, t: ParseTree): Doc { ] }, ] - | ParseTree.ChildClassTree{annotations, name, constructor} -> + | ParseTree.ChildClassTree{extension, annotations, name, constructor} -> Doc.Group[ + if (extension.isEmpty()) Doc.Empty() else { + Doc.Concat[print(ctx, extension), Doc.space] + }, printModifiers(ctx, annotations), print(ctx, name), print(ctx, constructor), ] - | ParseTree.ChildrenTree{childrenKeyword, equal, bar, childClasses} -> + | ParseTree.ChildrenTree{ + modifiers, + childrenKeyword, + equal, + bar, + childClasses, + } -> Doc.Group[ + printModifiers(ctx, modifiers), print(ctx, childrenKeyword), Doc.space, print(ctx, equal), diff --git a/skiplang/compiler/src/skipAst.sk b/skiplang/compiler/src/skipAst.sk index 4dfcaaed2..0051fc82b 100644 --- a/skiplang/compiler/src/skipAst.sk +++ b/skiplang/compiler/src/skipAst.sk @@ -156,6 +156,7 @@ type Children = List; class Child{ chi_range: FileRange, + chi_extension: ?FileRange, chi_name: Name, chi_params: ?Class_params, chi_annotations: SSet, diff --git a/skiplang/compiler/src/skipExpand.sk b/skiplang/compiler/src/skipExpand.sk index fdad9bb8d..9aa755f65 100644 --- a/skiplang/compiler/src/skipExpand.sk +++ b/skiplang/compiler/src/skipExpand.sk @@ -2858,7 +2858,7 @@ fun child( A.Class_def{ range => chi.chi_range, depth => -2, - extension => None(), + extension => chi.chi_extension, native_ => None(), kind => A.KClass(), value => None(), diff --git a/skiplang/compiler/tests/expand/extension_children_blanket.exp b/skiplang/compiler/tests/expand/extension_children_blanket.exp new file mode 100644 index 000000000..e635432cb --- /dev/null +++ b/skiplang/compiler/tests/expand/extension_children_blanket.exp @@ -0,0 +1 @@ +Hello from A diff --git a/skiplang/compiler/tests/expand/extension_children_blanket.sk b/skiplang/compiler/tests/expand/extension_children_blanket.sk new file mode 100644 index 000000000..e9a64cff9 --- /dev/null +++ b/skiplang/compiler/tests/expand/extension_children_blanket.sk @@ -0,0 +1,21 @@ +base class C { + extension children = + | A() + | B() +} + +class A { + fun hello(): String { + "Hello from A" + } +} + +class B { + fun hello(): String { + "Hello from B" + } +} + +fun main(): void { + print_string(A().hello()) +} diff --git a/skiplang/compiler/tests/expand/extension_children_per_child.exp b/skiplang/compiler/tests/expand/extension_children_per_child.exp new file mode 100644 index 000000000..e635432cb --- /dev/null +++ b/skiplang/compiler/tests/expand/extension_children_per_child.exp @@ -0,0 +1 @@ +Hello from A diff --git a/skiplang/compiler/tests/expand/extension_children_per_child.sk b/skiplang/compiler/tests/expand/extension_children_per_child.sk new file mode 100644 index 000000000..fc650b0fe --- /dev/null +++ b/skiplang/compiler/tests/expand/extension_children_per_child.sk @@ -0,0 +1,15 @@ +base class C { + children = + | extension A() + | B() +} + +class A { + fun hello(): String { + "Hello from A" + } +} + +fun main(): void { + print_string(A().hello()) +} diff --git a/skiplang/compiler/tests/syntax/invalid/modifier_children.exp_err b/skiplang/compiler/tests/syntax/invalid/modifier_children.exp_err index f4ba30b47..414628c05 100644 --- a/skiplang/compiler/tests/syntax/invalid/modifier_children.exp_err +++ b/skiplang/compiler/tests/syntax/invalid/modifier_children.exp_err @@ -1,5 +1,5 @@ File "tests/syntax/invalid/modifier_children.sk", line 2, characters 3-9: -Modifiers are not permitted on 'children' +Only 'extension' modifier is permitted on 'children' 1 | base class Foo { 2 | private children = A | ^^^^^^^