Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for annotations #247

Merged
merged 18 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
chengluyu marked this conversation as resolved.
Show resolved Hide resolved
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
chengluyu marked this conversation as resolved.
Show resolved Hide resolved
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
Loading