Skip to content

Commit

Permalink
Experimental support for annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
chengluyu committed Dec 4, 2024
1 parent 9986302 commit 7b66796
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 22 deletions.
4 changes: 4 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ class Lowering(using TL, Raise, Elaborator.State):
term(finallyDo)(_ => End()),
k(Value.Ref(l))
)

case Annotated(prefix, receiver) =>
// TODO: handle annotations
term(receiver)(k)

case Error => End("error")

Expand Down
4 changes: 2 additions & 2 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/BlockImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package semantics

import mlscript.utils.*, shorthands.*
import syntax.Tree.*
import hkmc2.syntax.TypeOrTermDef
import hkmc2.syntax.{Annotations, TypeOrTermDef}


trait BlockImpl(using Elaborator.State):
Expand All @@ -14,7 +14,7 @@ trait BlockImpl(using Elaborator.State):
val definedSymbols: Array[Str -> BlockMemberSymbol] =
desugStmts
.flatMap:
case td: syntax.TypeOrTermDef =>
case Annotations(_, td: syntax.TypeOrTermDef) =>
td.name match
case L(_) => Nil
case R(id) =>
Expand Down
10 changes: 10 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,13 @@ extends Importer:
case Spread(kw, kwLoc, body) =>
raise(ErrorReport(msg"Illegal position for '${kw.name}' spread operator." -> tree.toLoc :: Nil))
Term.Error
case Annotated(prefix, receiver) =>
val ann = prefix match
case App(_: Ident | _: SynthSel | _: Sel, _) | _: Ident | _: SynthSel | _: Sel => term(prefix)
case _ =>
raise(ErrorReport(msg"Unsupported annotation prefix." -> prefix.toLoc :: Nil))
Term.Error
Term.Annotated(ann, term(receiver))
// case _ =>
// ???

Expand Down Expand Up @@ -750,6 +757,9 @@ extends Importer:
case Modified(Keyword.`declare`, absLoc, body) :: sts =>
// TODO: pass declare to `go`
go(body :: sts, acc)
case Annotated(prefix, receiver) :: sts =>
// TODO: pass annotations to `go`
go(receiver :: sts, acc)
case (result: Tree) :: Nil =>
val res = term(result)
(Term.Blk(acc.reverse, res), ctx)
Expand Down
4 changes: 4 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ enum Term extends Statement:
case Throw(result: Term)
case Try(body: Term, finallyDo: Term)
case Handle(lhs: LocalSymbol, rhs: Term, defs: ObjBody)
case Annotated(prefix: Term, receiver: Term)

lazy val symbol: Opt[Symbol] = this match
case Ref(sym) => S(sym)
Expand Down Expand Up @@ -72,6 +73,7 @@ enum Term extends Statement:
case RegRef(reg, value) => "reference creation"
case Assgn(lhs, rhs) => "assignment"
case Deref(ref) => "dereference"
case Annotated(prefix, receiver) => "annotation"
end Term

import Term.*
Expand Down Expand Up @@ -126,6 +128,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
case Try(body, finallyDo) => body :: finallyDo :: Nil
case Handle(lhs, rhs, defs) => rhs :: defs._1 :: Nil
case Neg(e) => e :: Nil
case Annotated(prefix, receiver) => prefix :: receiver :: Nil

protected def children: Ls[Located] = this match
case t: Lit => t.lit.asTree :: Nil
Expand Down Expand Up @@ -196,6 +199,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
cls.tparams.map(_.showDbg).mkStringOr(", ", "[", "]")}${
cls.paramsOpt.fold("")(_.toString)} ${cls.body}"
case Import(sym, file) => s"import ${sym} from ${file}"
case Annotated(prefix, receiver) => s"@${prefix.showDbg} ${receiver.showDbg}"

final case class LetDecl(sym: LocalSymbol) extends Statement

Expand Down
55 changes: 35 additions & 20 deletions hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ object Parser:
loc.origin.fph.getLineColAt(loc.spanStart) match
case (ln, _, col) => s"Ln $ln Col $col"

extension (trees: Ls[Tree])
/** Note that the innermost annotation is the leftmost. */
def annotate(tree: Tree): Tree = trees.foldLeft(tree):
case (acc, ann) => Annotated(ann, acc)

end Parser
import Parser._

Expand Down Expand Up @@ -232,21 +237,24 @@ abstract class Parser(
maybeIndented((p, i) => p.block(allowNewlines = i))


def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, allowNewlines)
def block(allowNewlines: Bool)(using Line): Ls[Tree] = blockOf(prefixRules, Nil, allowNewlines)

def blockOf(rule: ParseRule[Tree], allowNewlines: Bool)(using Line): Ls[Tree] =
wrap(rule.name)(blockOfImpl(rule, allowNewlines))
def blockOfImpl(rule: ParseRule[Tree], allowNewlines: Bool): Ls[Tree] =
def blockContOf(rule: ParseRule[Tree]): Ls[Tree] =
def blockOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool)(using Line): Ls[Tree] =
wrap(rule.name)(blockOfImpl(rule, headAnnotations, allowNewlines))
def blockOfImpl(rule: ParseRule[Tree], headAnnotations: Ls[Tree], allowNewlines: Bool): Ls[Tree] =
def blockContOf(rule: ParseRule[Tree], headAnnotations: Ls[Tree] = Nil): Ls[Tree] =
yeetSpaces match
case (COMMA, _) :: _ => consume; blockOf(rule, allowNewlines)
case (SEMI, _) :: _ => consume; blockOf(rule, allowNewlines)
case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, allowNewlines)
case (COMMA, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines)
case (SEMI, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines)
case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines)
case _ => Nil
cur match
case Nil => Nil
case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, allowNewlines)
case (SPACE, _) :: _ => consume; blockOf(rule, allowNewlines)
case (NEWLINE, _) :: _ if allowNewlines => consume; blockOf(rule, headAnnotations, allowNewlines)
case (SPACE, _) :: _ => consume; blockOf(rule, headAnnotations, allowNewlines)
case (IDENT("@", _), l0) :: _ =>
consume
blockOf(rule, simpleExpr(AppPrec) :: headAnnotations, allowNewlines)
case (tok @ (id: IDENT), loc) :: _ =>
Keyword.all.get(id.name) match
case S(kw) =>
Expand All @@ -256,14 +264,14 @@ abstract class Parser(
yeetSpaces match
case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ if subRule.blkAlt.isEmpty =>
consume
val blk = rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.blockOf(subRule, allowNewlines)) // FIXME allowNewlines?
val blk = rec(toks, S(tok.innerLoc), tok.describe).concludeWith(_.blockOf(subRule, Nil, allowNewlines)) // FIXME allowNewlines?
if blk.isEmpty then
err((msg"Expected ${subRule.whatComesAfter} ${subRule.mkAfterStr}; found end of block instead" -> S(loc) :: Nil))
errExpr
blk ::: blockContOf(rule)
blk ::: blockContOf(rule) // TODO: apply headAnnotations
case _ =>
val res = parseRule(CommaPrecNext, subRule).getOrElse(errExpr)
exprCont(res, CommaPrecNext, false) :: blockContOf(rule)
headAnnotations.annotate(exprCont(res, CommaPrecNext, false)) :: blockContOf(rule)
case N =>

// TODO dedup this common-looking logic:
Expand All @@ -273,26 +281,29 @@ abstract class Parser(
yeetSpaces match
case (tok @ BRACKETS(Indent | Curly, toks), loc) :: _ /* if subRule.blkAlt.isEmpty */ =>
consume
headAnnotations match
case Nil => ()
case head :: _ =>
err((msg"Blocks are not allowed after annotations" -> head.toLoc :: Nil))
prefixRules.kwAlts.get(kw.name) match
case S(subRule) if subRule.blkAlt.isEmpty =>
rec(toks, S(tok.innerLoc), tok.describe).concludeWith { p =>
p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), allowNewlines)
p.blockOf(subRule.map(e => parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)), Nil, allowNewlines)
} ++ blockContOf(rule)
case _ =>
TODO(cur)
case _ =>
prefixRules.kwAlts.get(kw.name) match
case S(subRule) =>
val e = parseRule(CommaPrecNext, subRule).getOrElse(errExpr)
parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr) :: blockContOf(rule)
headAnnotations.annotate(parseRule(CommaPrecNext, exprAlt.rest).map(res => exprAlt.k(e, res)).getOrElse(errExpr)) :: blockContOf(rule)
case N =>
// TODO dedup?
err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil))
errExpr :: blockContOf(rule)
headAnnotations.annotate(errExpr) :: blockContOf(rule)
case N =>
err((msg"Expected ${rule.whatComesAfter} ${rule.mkAfterStr}; found ${tok.describe} instead" -> S(loc) :: Nil))
errExpr :: blockContOf(rule)

headAnnotations.annotate(errExpr) :: blockContOf(rule)
case N =>
val lhs = tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)
cur match
Expand All @@ -301,9 +312,9 @@ abstract class Parser(
val rhs = expr(CommaPrecNext)
Def(lhs, rhs) :: blockContOf(rule)
case _ =>
lhs :: blockContOf(rule)
headAnnotations.annotate(lhs) :: blockContOf(rule)
case (tok, loc) :: _ =>
tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr) :: blockContOf(rule)
headAnnotations.annotate(tryParseExp(CommaPrecNext, tok, loc, rule).getOrElse(errExpr)) :: blockContOf(rule)


private def tryParseExp[A](prec: Int, tok: Token, loc: Loc, rule: ParseRule[A]): Opt[A] =
Expand Down Expand Up @@ -467,6 +478,10 @@ abstract class Parser(
def simpleExpr(prec: Int)(using Line): Tree = wrap(prec)(simpleExprImpl(prec))
def simpleExprImpl(prec: Int): Tree =
yeetSpaces match
case (IDENT("@", _), l0) :: _ =>
consume
val ann = simpleExpr(AppPrec)
Annotated(ann, simpleExpr(prec))
case (IDENT(nme, sym), loc) :: _ =>
Keyword.all.get(nme) match
case S(kw) => // * Expressions starting with keywords should be handled in parseRule
Expand Down
8 changes: 8 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ enum Tree extends AutoLocated:
case RegRef(reg: Tree, value: Tree)
case Effectful(eff: Tree, body: Tree)
case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree])
case Annotated(prefix: Tree, receiver: Tree)

def children: Ls[Tree] = this match
case _: Empty | _: Error | _: Ident | _: Literal => Nil
Expand Down Expand Up @@ -107,6 +108,7 @@ enum Tree extends AutoLocated:
case Open(bod) => bod :: Nil
case Def(lhs, rhs) => lhs :: rhs :: Nil
case Spread(_, _, body) => body.toList
case Annotated(prefix, receiver) => prefix :: receiver :: Nil

def describe: Str = this match
case Empty() => "empty"
Expand Down Expand Up @@ -142,6 +144,7 @@ enum Tree extends AutoLocated:
case Handle(_, _, _, _) => "handle"
case Def(lhs, rhs) => "defining assignment"
case Spread(_, _, _) => "spread"
case Annotated(prefix, receiver) => "annotated"

def showDbg: Str = toString // TODO

Expand Down Expand Up @@ -191,6 +194,11 @@ object Apps:
def unapply(t: Tree): S[(Tree, Ls[Tup])] = t match
case App(Apps(id, args), arg: Tup) => S(id, args :+ arg)
case t => S(t, Nil)

object Annotations:
def unapply(t: Tree): Opt[(Ls[Tree], Tree)] = t match
case Annotated(p, Annotations(ps, recv)) => S(p :: ps, recv)
case other => S((Nil, other))


sealed abstract class OuterKind(val desc: Str)
Expand Down
71 changes: 71 additions & 0 deletions hkmc2/shared/src/test/mlscript/syntax/Annotations.mls
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
:js

module tailrec

tailrec
//│ = tailrec { class: [class tailrec] }

@tailrec fun fact_n(n, acc) =
if n == 0 then acc else fact(n - 1, n * acc)

fun fact(n) =
@tailrec fun go(n, acc) =
if n == 0 then acc else go(n - 1, n * acc)
go(n, 1)

module compile

class SomePattern

:e // TODO: pattern compilation
fun foo(x) = if x is @compile SomePattern then "yes" else "no"
//│ ╔══[ERROR] Unrecognized pattern.
//│ ║ l.21: fun foo(x) = if x is @compile SomePattern then "yes" else "no"
//│ ╙── ^^^^^^^^^^^^^^^^^^^

module debug

@debug 0
//│ = 0

@debug 1 + 2
//│ = 3

(@debug 1 + 2)
//│ = 3

(@debug 1) + 2
//│ = 3

(1 + @debug 2)
//│ = 3

class Log(msg: Str)

fact(@Log 5)
//│ = 120

class Freezed(degree: Num)

@Freezed(-273.15) class AbsoluteZero

@Freezed(-18) class Beverage(name: Str)

@Freezed(-4) let drink = Beverage("Coke")
//│ drink = Beverage { name: 'Coke' }

module Foo with
class Bar(qax: Str)

@Foo.Bar("baz") class Qux

@42 class Qux

:pe
@1 + 2 class Qux
//│ ╔══[PARSE ERROR] Expected end of input; found 'class' keyword instead
//│ ║ l.65: @1 + 2 class Qux
//│ ╙── ^^^^^
//│ = 2

@(1 + 2) class Qux

0 comments on commit 7b66796

Please sign in to comment.