Skip to content

Commit

Permalink
Merge pull request #40 from KacperFKorban/more-primitives
Browse files Browse the repository at this point in the history
More primitives + other small fixes
  • Loading branch information
KacperFKorban authored Apr 7, 2024
2 parents 4014977 + c6459f5 commit e8da5b8
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 16 deletions.
21 changes: 15 additions & 6 deletions guinep/src/main/scala/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,13 @@ private[guinep] object macros {
formElement

private def functionFormElementFromTree(paramName: String, paramType: TypeRepr)(using FormConstrContext): FormElement = paramType match {
case ntpe: NamedType if ntpe.name == "String" => FormElement.TextInput(paramName)
case ntpe: NamedType if ntpe.name == "Int" => FormElement.NumberInput(paramName)
case ntpe: NamedType if ntpe.name == "Boolean" => FormElement.CheckboxInput(paramName)
case ntpe: NamedType if ntpe =:= TypeRepr.of[String] => FormElement.TextInput(paramName)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Char] => FormElement.CharInput(paramName)
case ntpe: NamedType
if ntpe =:= TypeRepr.of[Int] || ntpe =:= TypeRepr.of[Long] || ntpe =:= TypeRepr.of[Short] || ntpe =:= TypeRepr.of[Byte] =>
FormElement.NumberInput(paramName)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Boolean] => FormElement.CheckboxInput(paramName)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Double] || ntpe =:= TypeRepr.of[Float] => FormElement.FloatingNumberInput(paramName)
case ntpe if isProductTpe(ntpe) =>
val classSymbol = ntpe.typeSymbol
val typeDefParams = classSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTypeParam)
Expand Down Expand Up @@ -218,9 +222,14 @@ private[guinep] object macros {

private def constructArg(paramTpe: TypeRepr, param: Term)(using ConstrContext): Term = {
paramTpe match {
case ntpe: NamedType if ntpe.name == "String" => param.select("asInstanceOf").appliedToType(ntpe)
case ntpe: NamedType if ntpe.name == "Int" => param.select("asInstanceOf").appliedToType(ntpe)
case ntpe: NamedType if ntpe.name == "Boolean" => param.select("asInstanceOf").appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[String] => param.select("asInstanceOf").appliedToType(ntpe)
case ntpe: NamedType if ntpe =:= TypeRepr.of[Char] => param.select("asInstanceOf").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(TypeRepr.of[Long]).select(s"to${ntpe.name}")
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(TypeRepr.of[Double]).select(s"to${ntpe.name}")
case ntpe if isCaseObjectTpe(ntpe) && ntpe.typeSymbol.flags.is(Flags.Module) =>
Ref(ntpe.typeSymbol.companionModule)
case ntpe if isCaseObjectTpe(ntpe) =>
Expand Down
13 changes: 13 additions & 0 deletions guinep/src/main/scala/model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ private[guinep] object model {

enum FormElement(val name: String):
case TextInput(override val name: String) extends FormElement(name)
case CharInput(override val name: String) extends FormElement(name)
case NumberInput(override val name: String) extends FormElement(name)
case FloatingNumberInput(override val name: String) extends FormElement(name)
case CheckboxInput(override val name: String) extends FormElement(name)
case Dropdown(override val name: String, options: List[(String, FormElement)]) extends FormElement(name)
case TextArea(override val name: String, rows: Option[Int] = None, cols: Option[Int] = None) extends FormElement(name)
Expand All @@ -36,7 +38,9 @@ private[guinep] object model {

def constrOrd: Int = this match
case TextInput(_) => 0
case CharInput(_) => 0
case NumberInput(_) => 1
case FloatingNumberInput(_) => 1
case CheckboxInput(_) => 2
case Dropdown(_, _) => 3
case TextArea(_, _, _) => 4
Expand All @@ -51,8 +55,12 @@ private[guinep] object model {
s"""{ "name": '$name', "type": 'fieldset', "elements": [${elements.map(_.toJSONRepr).mkString(",")}] }"""
case FormElement.TextInput(name) =>
s"""{ "name": '$name', "type": 'text' }"""
case FormElement.CharInput(name) =>
s"""{ "name": '$name', "type": 'char' }"""
case FormElement.NumberInput(name) =>
s"""{ "name": '$name', "type": 'number' }"""
case FormElement.FloatingNumberInput(name) =>
s"""{ "name": '$name', "type": 'float' }"""
case FormElement.CheckboxInput(name) =>
s"""{ "name": '$name', "type": 'checkbox' }"""
case FormElement.Dropdown(name, options) =>
Expand All @@ -76,8 +84,12 @@ private[guinep] object model {
'{ FormElement.FieldSet(${Expr(name)}, ${Expr(elements)}) }
case FormElement.TextInput(name) =>
'{ FormElement.TextInput(${Expr(name)}) }
case FormElement.CharInput(name) =>
'{ FormElement.CharInput(${Expr(name)}) }
case FormElement.NumberInput(name) =>
'{ FormElement.NumberInput(${Expr(name)}) }
case FormElement.FloatingNumberInput(name) =>
'{ FormElement.FloatingNumberInput(${Expr(name)}) }
case FormElement.CheckboxInput(name) =>
'{ FormElement.CheckboxInput(${Expr(name)}) }
case FormElement.Dropdown(name, options) =>
Expand All @@ -93,6 +105,7 @@ private[guinep] object model {
case FormElement.NamedRef(name, ref) =>
'{ FormElement.NamedRef(${Expr(name)}, ${Expr(ref)}) }

// This ordering is a hack to avoid placing recursive constructors as first options in a dropdown
given Ordering[FormElement] = new Ordering[FormElement] {
def compare(x: FormElement, y: FormElement): Int =
if x.constrOrd < y.constrOrd then -1
Expand Down
64 changes: 64 additions & 0 deletions guinep/src/test/scala/formgentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,70 @@ class FormGenTests extends munit.FunSuite {
)
)

checkGeneratedFormEquals(
"showDouble",
showDouble,
Form(
Seq(
FormElement.FloatingNumberInput("d")
),
Map.empty
)
)

checkGeneratedFormEquals(
"multiplyShorts",
multiplyShorts,
Form(
Seq(
FormElement.NumberInput("a"),
FormElement.NamedRef("b", "scala.Short")
),
Map(
"scala.Short" -> FormElement.NumberInput("value")
)
)
)

checkGeneratedFormEquals(
"divideFloats",
divideFloats,
Form(
Seq(
FormElement.FloatingNumberInput("a"),
FormElement.NamedRef("b", "scala.Float")
),
Map(
"scala.Float" -> FormElement.FloatingNumberInput("value")
)
)
)

checkGeneratedFormEquals(
"subtractLongs",
subtractLongs,
Form(
Seq(
FormElement.NumberInput("a"),
FormElement.NamedRef("b", "scala.Long")
),
Map(
"scala.Long" -> FormElement.NumberInput("value")
)
)
)

checkGeneratedFormEquals(
"codeOfChar",
codeOfChar,
Form(
Seq(
FormElement.CharInput("c")
),
Map.empty
)
)

checkGeneratedFormEquals(
"isInTree",
isInTree,
Expand Down
43 changes: 39 additions & 4 deletions guinep/src/test/scala/rungentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class RunGenTests extends munit.FunSuite {
checkGeneratedRunResultEquals(
"add",
add,
List(1, 2),
List(1l, 2l),
"3"
)

Expand Down Expand Up @@ -55,7 +55,7 @@ class RunGenTests extends munit.FunSuite {
checkGeneratedRunResultEquals(
"addObj",
addObj,
List(Map("a" -> 1, "b" -> 2)),
List(Map("a" -> 1l, "b" -> 2l)),
"3"
)

Expand Down Expand Up @@ -158,17 +158,52 @@ class RunGenTests extends munit.FunSuite {
"hello"
)

checkGeneratedRunResultEquals(
"showDouble",
showDouble,
List(1.0d),
"1.0"
)

checkGeneratedRunResultEquals(
"multiplyShorts",
multiplyShorts,
List(1l, 2l),
"2"
)

checkGeneratedRunResultEquals(
"divideFloats",
divideFloats,
List(1.0d, 2.0d),
"0.5"
)

checkGeneratedRunResultEquals(
"subtractLongs",
subtractLongs,
List(2l, 1l),
"1"
)

checkGeneratedRunResultEquals(
"codeOfChar",
codeOfChar,
List('a'),
"97"
)

checkGeneratedRunResultEquals(
"isInTree",
isInTree,
List(1, Map("name" -> "Node", "value" -> Map("left" -> Map("name" -> "Leaf", "value" -> Map.empty), "value" -> 1, "right" -> Map("name" -> "Leaf", "value" -> Map.empty)))),
List(1l, Map("name" -> "Node", "value" -> Map("left" -> Map("name" -> "Leaf", "value" -> Map.empty), "value" -> 1l, "right" -> Map("name" -> "Leaf", "value" -> Map.empty)))),
"true"
)

checkGeneratedRunResultEquals(
"isInTree",
isInTree,
List(1, Map("name" -> "Leaf", "value" -> Map.empty)),
List(1l, Map("name" -> "Leaf", "value" -> Map.empty)),
"false"
)

Expand Down
15 changes: 15 additions & 0 deletions guinep/src/test/scala/testsdata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ object TestsData {
def concatAll(elems: List[String]): String =
elems.mkString

def showDouble(d: Double): String =
d.toString

def multiplyShorts(a: Short, b: Short): Int =
a * b

def divideFloats(a: Float, b: Float): Float =
a / b

def subtractLongs(a: Long, b: Long): Long =
a - b

def codeOfChar(c: Char): Int =
c.toInt

enum IntTree:
case Leaf
case Node(left: IntTree, value: Int, right: IntTree)
Expand Down
12 changes: 12 additions & 0 deletions testcases/src/main/scala/main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ def printsWeirdGADT(g: WeirdGADT[String]): String = g match
def concatAll(elems: List[String]): String =
elems.mkString

def showDouble(d: Double): String =
d.toString

def divideFloats(a: Float, b: Float): Float =
a / b

def codeOfChar(c: Char): Int =
c.toInt

enum IntTree:
case Leaf
case Node(left: IntTree, value: Int, right: IntTree)
Expand Down Expand Up @@ -99,6 +108,9 @@ def run: Unit =
roll20,
roll6(),
concatAll,
showDouble,
divideFloats,
codeOfChar,
isInTree,
// isInTreeExt
// addManyParamLists
Expand Down
2 changes: 1 addition & 1 deletion web/src/main/scala/api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ inline def web(inline functions: Any*): Unit =
|Ignoring duplicates""".stripMargin
)
println("Starting GUInep web server at http://localhost:8090/")
webgen.genWeb(functionsInfosMap.view.mapValues(_.head).toMap)
webgen.genWeb(functionsInfos.distinct.map(fun => fun.name -> fun))
28 changes: 27 additions & 1 deletion web/src/main/scala/htmlgen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import zio.http.template.*
import zio.http.codec.*

private[guinep] trait HtmlGen {
val funs: Map[String, Fun]
val funs: Seq[(String, Fun)]
def generateHtml =
html(
head(
Expand Down Expand Up @@ -125,6 +125,32 @@ private[guinep] trait HtmlGen {
const formElemFromLookup = namedLookup[formElem.ref];
formElemFromLookup.name = formElem.name;
addFormElement(form, formElemFromLookup, namedLookup);
} else if (formElem.type == 'float') {
const label = document.createElement('label');
label.innerText = formElem.name + ': ';
label.for = formElem.name;
form.appendChild(label);
const input = document.createElement('input');
input.type = 'number';
input.step = 'any';
input.name = formElem.name;
input.id = formElem.name;
input.placeholder = formElem.name;
form.appendChild(input);
form.appendChild(br.cloneNode());
} else if (formElem.type == 'char') {
const label = document.createElement('label');
label.innerText = formElem.name + ': ';
label.for = formElem.name;
form.appendChild(label);
const input = document.createElement('input');
input.type = 'text';
input.maxLength = '1';
input.name = formElem.name;
input.id = formElem.name;
input.placeholder = formElem.name;
form.appendChild(input);
form.appendChild(br.cloneNode());
} else {
const label = document.createElement('label');
label.innerText = formElem.name + ': ';
Expand Down
4 changes: 3 additions & 1 deletion web/src/main/scala/serialization.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ private[guinep] object serialization:
res <- elements.parseJSONValue(m)
} yield res
case FormElement.TextInput(_) => value.asString.toRight(s"Invalid string: $value")
case FormElement.NumberInput(_) => value.asString.flatMap(_.toIntOption).toRight(s"Invalid number: $value")
case FormElement.CharInput(_) => value.asString.flatMap(_.headOption).toRight(s"Invalid char: $value")
case FormElement.NumberInput(_) => value.asString.flatMap(_.toLongOption).toRight(s"Invalid number: $value")
case FormElement.FloatingNumberInput(_) => value.asString.flatMap(_.toDoubleOption).toRight(s"Invalid float: $value")
case FormElement.CheckboxInput(_) => value.asBoolean.toRight(s"Invalid boolean: $value")
case FormElement.Dropdown(_, options) =>
for {
Expand Down
7 changes: 4 additions & 3 deletions web/src/main/scala/webgen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.util.chaining.*

private[guinep] object webgen {

def genWeb(funs: Map[String, Fun]): Unit = {
def genWeb(funs: Seq[(String, Fun)]): Unit = {
val ws = WebServer(funs)
val runtime = Runtime.default
Unsafe.unsafe { implicit unsafe =>
Expand All @@ -24,7 +24,8 @@ private[guinep] object webgen {
}
}

class WebServer(val funs: Map[String, Fun]) extends HtmlGen {
class WebServer(val funs: Seq[(String, Fun)]) extends HtmlGen {
val funsMap = funs.toMap
val app: HttpApp[Any] = Routes(
Method.GET / PathCodec.empty ->
handler(Response.html(generateHtml)),
Expand All @@ -35,7 +36,7 @@ private[guinep] object webgen {
(for {
str <- req.body.asString
obj <- ZIO.fromEither(str.fromJson[Obj])
fun = funs(name)
fun = funsMap(name)
given Map[String, FormElement] = fun.form.namedFormElements
inputsValuesMap <- ZIO.fromEither(fun.form.inputs.toList.parseJSONValue(obj))
inputsValues = fun.form.inputs.toList.sortByArgs(inputsValuesMap)
Expand Down

0 comments on commit e8da5b8

Please sign in to comment.