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

Missing primitives #47

Merged
merged 2 commits into from
Apr 14, 2024
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
63 changes: 39 additions & 24 deletions guinep/src/main/scala/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,20 @@ private[guinep] object macros {
extension (t: Term)
private def select(s: Term): Term = Select(t, s.symbol)
private def select(s: String): Term =
t.select(
t.tpe
.typeSymbol
.methodMember(s)
.headOption.
getOrElse(report.errorAndAbort(s"PANIC: No member $s in term ${t.show} with type ${t.tpe.show}"))
)
val sym = t.tpe
.typeSymbol
.methodMember(s)
.headOption
.getOrElse(report.errorAndAbort(s"PANIC: No member $s in term ${t.show} with type ${t.tpe.show}"))
Select(t, sym)
private def select(s: String, argnum: Int): Term =
val sym = t.tpe
.typeSymbol
.methodMember(s)
.filter(_.paramSymss.flatten.size == argnum)
.headOption
.getOrElse(report.errorAndAbort(s"PANIC: No member $s in term ${t.show} with type ${t.tpe.show}"))
Select(t, sym)

extension (s: Symbol)
private def prettyName: String =
Expand Down Expand Up @@ -133,12 +140,18 @@ private[guinep] object macros {
FormElement.NumberInput(paramName, Types.IntType.Short)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Byte] =>
FormElement.NumberInput(paramName, Types.IntType.Byte)
case ntpe: NamedType if ntpe =:= TypeRepr.of[BigInt] =>
FormElement.NumberInput(paramName, Types.IntType.BigInt)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Boolean] =>
FormElement.CheckboxInput(paramName)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Float] =>
FormElement.FloatingNumberInput(paramName, Types.FloatingType.Float)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Double] =>
FormElement.FloatingNumberInput(paramName, Types.FloatingType.Double)
case ntpe: NamedType if ntpe =:= TypeRepr.of[BigDecimal] =>
FormElement.FloatingNumberInput(paramName, Types.FloatingType.BigDecimal)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Unit] =>
FormElement.HiddenInput(paramName, "Unit")
case AppliedType(ntpe: NamedType, List(tpeArg)) if listLikeSymbolsTypes.contains(ntpe.typeSymbol) =>
FormElement.ListInput(paramName, functionFormElementFromTreeWithCaching("elem", tpeArg), listLikeSymbolsTypes(ntpe.typeSymbol))
case OrType(ltpe, rtpe) if ltpe =:= TypeRepr.of[Null] || rtpe =:= TypeRepr.of[Null] =>
Expand Down Expand Up @@ -249,20 +262,22 @@ private[guinep] object macros {
private def constructArg(paramTpe: TypeRepr, param: Term)(using ConstrContext): Term = {
paramTpe match {
case ntpe: NamedType if ntpe =:= TypeRepr.of[String] =>
param.select("asInstanceOf").appliedToType(ntpe)
param.select("asInstanceOf", 1).appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Char] =>
param.select("asInstanceOf").appliedToType(ntpe)
param.select("asInstanceOf", 1).appliedToType(ntpe)
case ntpe: NamedType
if ntpe =:= TypeRepr.of[Int] || ntpe =:= TypeRepr.of[Long] || ntpe =:= TypeRepr.of[Short] || ntpe =:= TypeRepr.of[Byte] =>
param.select(s"asInstanceOf").appliedToType(ntpe)
if ntpe =:= TypeRepr.of[Int] || ntpe =:= TypeRepr.of[Long] || ntpe =:= TypeRepr.of[Short] || ntpe =:= TypeRepr.of[Byte] || ntpe =:= TypeRepr.of[BigInt] =>
param.select(s"asInstanceOf", 1).appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Boolean] =>
param.select("asInstanceOf").appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Double] || ntpe =:= TypeRepr.of[Float] =>
param.select(s"asInstanceOf").appliedToType(ntpe)
param.select("asInstanceOf", 1).appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Double] || ntpe =:= TypeRepr.of[Float] || ntpe =:= TypeRepr.of[BigDecimal] =>
param.select(s"asInstanceOf", 1).appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Unit] =>
param.select("asInstanceOf", 1).appliedToType(ntpe)
case AppliedType(ntpe: NamedType, List(tpeArg)) if listLikeSymbolsTypes.contains(ntpe.typeSymbol) =>
param.select("asInstanceOf").appliedToType(paramTpe)
param.select("asInstanceOf", 1).appliedToType(paramTpe)
case OrType(ltpe, rtpe) if ltpe =:= TypeRepr.of[Null] || rtpe =:= TypeRepr.of[Null] =>
val castedParam = param.select("asInstanceOf").appliedToType(paramTpe)
val castedParam = param.select("asInstanceOf", 1).appliedToType(paramTpe)
'{ if ${param.asExpr} == null then null else ${castedParam.asExpr} }.asTerm
case ntpe if isCaseObjectTpe(ntpe) && ntpe.typeSymbol.flags.is(Flags.Module) =>
Ref(ntpe.typeSymbol.companionModule)
Expand All @@ -275,7 +290,7 @@ private[guinep] object macros {
val paramValue = '{ ${param.asExpr}.asInstanceOf[Map[String, Any]] }.asTerm
val args = fields.collect { case field: ValDef =>
val fieldName = field.name
val fieldValue = paramValue.select("apply").appliedTo(Literal(StringConstant(fieldName)))
val fieldValue = paramValue.select("apply", 1).appliedTo(Literal(StringConstant(fieldName)))
constructArgWithCaching(
field.tpt.tpe.substituteTypes(typeDefParams, ntpe.typeArgs),
fieldValue
Expand All @@ -288,14 +303,14 @@ private[guinep] object macros {
val children = classSymbol.children
val childrenAppliedTpes = children.map(child => appliedChild(child, classSymbol, ntpe.typeArgs)).map(_.stripAnnots)
val paramMap = '{ ${param.asExpr}.asInstanceOf[Map[String, Any]] }.asTerm
val paramName = paramMap.select("apply").appliedTo(Literal(StringConstant("name")))
val paramValue = paramMap.select("apply").appliedTo(Literal(StringConstant("value")))
val paramName = paramMap.select("apply", 1).appliedTo(Literal(StringConstant("name")))
val paramValue = paramMap.select("apply", 1).appliedTo(Literal(StringConstant("value")))
children.zip(childrenAppliedTpes).foldRight[Term]{
'{ throw new RuntimeException(s"Class ${${paramName.asExpr}} is not a child of ${${Expr(className)}}") }.asTerm
} { case ((child, childAppliedTpe), acc) =>
val childName = Literal(StringConstant(child.prettyName))
If(
paramName.select("equals").appliedTo(childName),
paramName.select("equals", 1).appliedTo(childName),
constructArgWithCaching(childAppliedTpe, paramValue),
acc
)
Expand All @@ -316,7 +331,7 @@ private[guinep] object macros {
{ case (sym, List(params: Term)) =>
val args = functionParams(f).zipWithIndex.map { case (valdef, i) =>
val paramTpe = valdef.tpt.tpe
val param = params.select("apply").appliedTo(Literal(IntConstant(i)))
val param = params.select("apply", 1).appliedTo(Literal(IntConstant(i)))
constructArgWithCaching(paramTpe, param)
}.toList
val aply = l.select("apply")
Expand All @@ -325,7 +340,7 @@ private[guinep] object macros {
aply.appliedToNone
else
aply.appliedToArgs(args)
res.select("toString").appliedToNone
res.select("toString", 0).appliedToNone
}
)
Block(
Expand All @@ -337,15 +352,15 @@ private[guinep] object macros {
Symbol.spliceOwner,
MethodType(List("inputs"))(_ => List(TypeRepr.of[List[Any]]), _ => TypeRepr.of[String]),
{ case (sym, List(params: Term)) =>
t.select("toString").appliedToNone
t.select("toString", 0).appliedToNone
}
).asExprOf[List[Any] => String]
case a@Apply(Ident(_), Nil) =>
Lambda(
Symbol.spliceOwner,
MethodType(List("inputs"))(_ => List(TypeRepr.of[List[Any]]), _ => TypeRepr.of[String]),
{ case (sym, List(params: Term)) =>
a.select("toString").appliedToNone
a.select("toString", 0).appliedToNone
}
).asExprOf[List[Any] => String]
case _ =>
Expand Down
10 changes: 10 additions & 0 deletions guinep/src/main/scala/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private[guinep] object model {
enum FloatingType:
case Double
case Float
case BigDecimal

object FloatingType:
given ToExpr[FloatingType] with
Expand All @@ -24,12 +25,15 @@ private[guinep] object model {
'{ FloatingType.Double }
case FloatingType.Float =>
'{ FloatingType.Float }
case FloatingType.BigDecimal =>
'{ FloatingType.BigDecimal }

enum IntType:
case Int
case Long
case Byte
case Short
case BigInt

object IntType:
given ToExpr[IntType] with
Expand All @@ -42,6 +46,8 @@ private[guinep] object model {
'{ IntType.Byte }
case IntType.Short =>
'{ IntType.Short }
case IntType.BigInt =>
'{ IntType.BigInt }

enum ListType:
case List
Expand All @@ -65,6 +71,7 @@ private[guinep] object model {
case NumberInput(override val name: String, underlying: Types.IntType) extends FormElement(name)
case FloatingNumberInput(override val name: String, underlying: Types.FloatingType) extends FormElement(name)
case CheckboxInput(override val name: String) extends FormElement(name)
case HiddenInput(override val name: String, underlying: String) extends FormElement(name)
case Dropdown(override val name: String, options: List[(String, FormElement)]) extends FormElement(name)
case ListInput(override val name: String, element: FormElement, underlying: Types.ListType) extends FormElement(name)
case TextArea(override val name: String, rows: Option[Int] = None, cols: Option[Int] = None) extends FormElement(name)
Expand All @@ -80,6 +87,7 @@ private[guinep] object model {
case CharInput(_) => 0
case NumberInput(_, _) => 1
case FloatingNumberInput(_, _) => 1
case HiddenInput(_, _) => 1
case CheckboxInput(_) => 2
case Dropdown(_, _) => 3
case ListInput(_, _, _) => 3
Expand All @@ -106,6 +114,8 @@ private[guinep] object model {
'{ FormElement.FloatingNumberInput(${Expr(name)}, ${Expr(underlying)}) }
case FormElement.CheckboxInput(name) =>
'{ FormElement.CheckboxInput(${Expr(name)}) }
case FormElement.HiddenInput(name, underlying) =>
'{ FormElement.HiddenInput(${Expr(name)}, ${Expr(underlying)}) }
case FormElement.Dropdown(name, options) =>
'{ FormElement.Dropdown(${Expr(name)}, ${Expr(options)}) }
case FormElement.ListInput(name, element, underlying) =>
Expand Down
33 changes: 33 additions & 0 deletions guinep/src/test/scala/formgentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,39 @@ class FormGenTests extends munit.FunSuite {
)
)

checkGeneratedFormEquals(
"factorialBigInt",
factorialBigInt,
Form(
Seq(
FormElement.NumberInput("n", Types.IntType.BigInt)
),
Map.empty
)
)

checkGeneratedFormEquals(
"inverseBigDecimal",
inverseBigDecimal,
Form(
Seq(
FormElement.FloatingNumberInput("bd", Types.FloatingType.BigDecimal)
),
Map.empty
)
)

checkGeneratedFormEquals(
"sayBye",
sayBye,
Form(
Seq(
FormElement.HiddenInput("unit", "Unit")
),
Map.empty
)
)

checkGeneratedFormEquals(
"isInTree",
isInTree,
Expand Down
21 changes: 21 additions & 0 deletions guinep/src/test/scala/rungentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,27 @@ class RunGenTests extends munit.FunSuite {
"1"
)

checkGeneratedRunResultEquals(
"factorialBigInt",
factorialBigInt,
List(BigInt(5)),
"120"
)

checkGeneratedRunResultEquals(
"inverseBigDecimal",
inverseBigDecimal,
List(BigDecimal(2)),
"0.5"
)

checkGeneratedRunResultEquals(
"sayBye",
sayBye,
List("Unit"),
"Bye!"
)

checkGeneratedRunResultEquals(
"isInTree",
isInTree,
Expand Down
9 changes: 9 additions & 0 deletions guinep/src/test/scala/testsdata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ object TestsData {
def showNullableInt(i: Int | Null): String =
if i == null then "null" else i.toString

def factorialBigInt(n: BigInt): BigInt =
if n == 0 then 1 else n * factorialBigInt(n - 1)

def inverseBigDecimal(bd: BigDecimal): BigDecimal =
BigDecimal(1) / bd

def sayBye(unit: Unit): String =
"Bye!"

enum IntTree:
case Leaf
case Node(left: IntTree, value: Int, right: IntTree)
Expand Down
21 changes: 19 additions & 2 deletions testcases/src/main/scala/main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,23 @@ def seqProduct(seq: Seq[Float]): Float =
def showNullableInt(i: Int | Null): String =
if i == null then "null" else i.toString

def factorialBigInt(n: BigInt): BigInt =
if n == 0 then 1 else n * factorialBigInt(n - 1)

def inverseBigDecimal(bd: BigDecimal): BigDecimal =
BigDecimal(1) / bd

def sayBye(unit: Unit): String =
"Bye!"

case class TakesUnit(unit: Unit)

def runTakesUnit(takesUnit: TakesUnit): Unit =
()

@main
def run: Unit =
guinep.web
.withModifyConfig(_.copy(requireNonNullableInputs = true))
.apply(
upperCaseText,
add,
Expand All @@ -129,7 +142,11 @@ def run: Unit =
listProduct,
sumVector,
seqProduct,
showNullableInt
showNullableInt,
factorialBigInt,
inverseBigDecimal,
sayBye,
runTakesUnit,
// isInTreeExt
// addManyParamLists
// printsWeirdGADT
Expand Down
19 changes: 11 additions & 8 deletions web/src/main/scala/htmlgen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ private[guinep] trait HtmlGen {
style(
"""
body { font-family: Arial, sans-serif; }
.sidebar { position: fixed; left: 0; top: 0; width: 200px; height: 100vh; background-color: #f0f0f0; padding: 20px; }
.sidebar { position: fixed; left: 0; top: 0; width: 200px; height: 100vh; background-color: #f0f0f0; padding: 20px; overflow-y: auto; }
.sidebar a { display: block; padding: 10px; margin-bottom: 10px; background-color: #007bff; color: white; text-decoration: none; text-align: center; border-radius: 5px; }
.sidebar a:hover { background-color: #0056b3; }
.main-content { margin-left: 232px; padding: 40px; display: flex; justify-content: center; padding-top: 20px; }
Expand All @@ -29,8 +29,8 @@ private[guinep] trait HtmlGen {
input[type=submit]:hover { background-color: #45a049; }
select { display: inline-block; margin-bottom: 10px; padding: 8px; box-sizing: border-box; }
.result { margin-top: 20px; font-weight: bold; }
.button { text-decoration: none; background-color: #AAAAAA; color: white; padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; }
.button:hover { background-color: #BBBBBB; }
.add-button { text-decoration: none; background-color: #AAAAAA; color: white; padding: 5px 10px; border: none; border-radius: 4px; cursor: pointer; }
.add-button:hover { background-color: #BBBBBB; }
"""
),
script(Dom.raw(jsToChangeFormBasedOnPath)),
Expand Down Expand Up @@ -138,7 +138,7 @@ private[guinep] trait HtmlGen {
const button = document.createElement('a');
button.innerText = '+';
button.href = '#';
button.classList.add('button');
button.classList.add('add-button');
button.onclick = () => {
addFormElement(fieldset, formElem.element, namedLookup, button);
};
Expand Down Expand Up @@ -187,13 +187,16 @@ private[guinep] trait HtmlGen {
form.insertBefore(input, before);
form.insertBefore(br.cloneNode(), before);
} else {
const label = document.createElement('label');
label.innerText = formElem.name + ': ';
label.for = formElem.name;
form.insertBefore(label, before);
if (formElem.type !== 'hidden') {
const label = document.createElement('label');
label.innerText = formElem.name + ': ';
label.for = formElem.name;
form.insertBefore(label, before);
}
const input = document.createElement('input');
input.type = formElem.type;
input.name = formElem.name;
input.value = formElem.value;
input.id = formElem.name;
input.placeholder = formElem.name;
if (formElem.nullable) {
Expand Down
Loading
Loading