Skip to content

Commit 587ee69

Browse files
committed
Better validation of lists of AstNodes throughout the lexicon to avoid generating gibberish output due to type-erasure
1 parent 9f2fa53 commit 587ee69

File tree

1 file changed

+24
-12
lines changed

1 file changed

+24
-12
lines changed

src/main/scala/wordbots/Lexicon.scala

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ object Lexicon {
4545

4646
implicit def astNodeToSem(node: ParseNode): Sem = Form(node)
4747

48+
/** Type checks that the given Seq has only elements of the expected case class (using .getClass()).
49+
* Sadly necessary because of type erasure ...
50+
* The .getClass().toString() approach requires an exact class name match, so this will only work for case classes, not traits.
51+
* TODO(AN): Come up with a more general approach that would work for inherited traits as well? */
52+
def validatingSeq[T, U](seq: Seq[T], expectedClassName: String)(out: U): U = {
53+
if (!seq.forall(_.getClass().toString() == "class wordbots.Semantics$" + expectedClassName)) {
54+
Fail(s"Seq parameter failed type check")
55+
}
56+
out
57+
}
58+
4859
val lexicon: ParserDict[CcgCat] = ParserDict[CcgCat]() +
4960
(Seq("a", "an") -> Seq(
5061
(N/N, identity),
@@ -331,15 +342,15 @@ object Lexicon {
331342
("gain".s -> Seq( // "[All robots] gain ..."
332343
((S\NP)/NP, λ {aa: AttributeAmount => λ {t: TargetObject => ModifyAttribute(t, aa.attr, Plus(aa.amount))}}), // " ... X attack"
333344
((S/N)\NP, λ {t: TargetObject => λ {attrs: Seq[AttributeAmount] => // "... X attack and Y speed"
334-
MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Plus(a.amount))))}}),
345+
validatingSeq(attrs, "AttributeAmount") { MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Plus(a.amount))))}}}),
335346
((S/NP)\NP, λ {p: TargetPlayer => λ {e: Energy => ModifyEnergy(p, Plus(e.amount))}}), // Y gains X energy.
336347
(((S\NP)/N)/Num, λ {num: Number => λ {a: Attribute => λ {t: TargetObject => ModifyAttribute(t, a, Plus(num))}}}) // Y gains X (attribute).
337348
)) +
338349
("get".s -> (((S/N)/Num)\NP, λ {t: TargetObject => λ {i: Scalar => λ {a: Attribute => SetAttribute(t, a, i)}}})) + // "All robots get X attack"))
339350
(("get".s ++ "gain".s) -> Seq( // "[All robots] get/gain ..."
340351
((S/NP)\NP, λ {t: TargetObject => λ {op: AttributeOperation => ModifyAttribute(t, op.attr, op.op)}}), // "... +X attack"
341352
((S/NP)\NP, λ {t: TargetObject => λ {ops: Seq[AttributeOperation] => // "... +X attack and +Y speed"
342-
MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}),
353+
validatingSeq(ops, "AttributeOperation") { MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}}),
343354
((S/S)\NP, λ {t: TargetObject => λ {a: Ability => GiveAbility(t, a)}}), // "... [ability]"
344355
((S/S)\NP, λ {t: TargetObject => λ {a: (AttributeOperation, Ability) => // "... +X attack and [ability]"
345356
MultipleActions(Seq(SaveTarget(t), ModifyAttribute(SavedTargetObject, a._1.attr, a._1.op), GiveAbility(SavedTargetObject, a._2)))}}),
@@ -352,9 +363,9 @@ object Lexicon {
352363
(((S/PP)/N)/Num, λ {i: Scalar => λ {a: Attribute => λ {t: TargetObject => ModifyAttribute(t, a, Plus(i))}}}), // "Give X attack [to a robot]"
353364
(((S/PP)/N)/Adj, λ {o: Operation => λ {a: Attribute => λ {t: TargetObject => ModifyAttribute(t, a, o)}}}), // "Give +X attack [to a robot]"
354365
((S/N)/NP, λ {t: TargetObject => λ {attrs: Seq[AttributeAmount] => // "... X attack and Y speed"
355-
MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Plus(a.amount))))}}),
366+
validatingSeq(attrs, "AttributeAmount") { MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Plus(a.amount))))}}}),
356367
((S/NP)/NP, λ {t: TargetObject => λ {ops: Seq[AttributeOperation] => // "... +X attack and +Y speed"
357-
MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}),
368+
validatingSeq(ops, "AttributeOperation") { MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}}),
358369
((S/S)/NP, λ {t: TargetObject => λ {a: Ability => GiveAbility(t, a)}}), // "... [ability]"
359370
((S/S)/NP, λ {t: TargetObject => λ {a: (AttributeOperation, Ability) => // "... +X attack and [ability]"
360371
MultipleActions(Seq(SaveTarget(t), ModifyAttribute(SavedTargetObject, a._1.attr, a._1.op), GiveAbility(SavedTargetObject, a._2)))}})
@@ -378,7 +389,7 @@ object Lexicon {
378389
)) +
379390
(Seq("has", "have") -> Seq(
380391
(S/NP, λ {ac: AttributeComparison => ac}),
381-
(S/NP, λ {cs: Seq[AttributeComparison] => cs}), // multiple conditions
392+
(S/NP, λ {cs: Seq[AttributeComparison] => validatingSeq(cs, "AttributeComparison") { cs }}), // multiple conditions
382393
((S\NP)/S, λ {a: Ability => λ {t: TargetObject => HasAbility(t, a)}}),
383394
((S\NP)/N, λ {a: AttributeAmount => λ {t: TargetObject => AttributeAdjustment(t, a.attr, Constant(a.amount))}}), // "... X attack"
384395
((S/NP)\NP, λ {t: TargetObject => λ {op: AttributeOperation => AttributeAdjustment(t, op.attr, op.op)}}), // "... +X attack"
@@ -434,7 +445,7 @@ object Lexicon {
434445
((S/NP)\NP, λ {p: TargetPlayer => λ {e: Energy => ModifyEnergy(p, Minus(e.amount))}}), // Y loses X energy.
435446
((S\NP)/NP, λ {aa: AttributeAmount => λ {t: TargetObject => ModifyAttribute(t, aa.attr, Minus(aa.amount))}}), // " ... X attack"
436447
((S/N)\NP, λ {t: TargetObject => λ {attrs: Seq[AttributeAmount] => // "... X attack and Y speed"
437-
MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Minus(a.amount))))}}),
448+
validatingSeq(attrs, "AttributeAmount") { MultipleActions(Seq(SaveTarget(t)) ++ attrs.map(a => ModifyAttribute(SavedTargetObject, a.attr, Minus(a.amount))))}}}),
438449
(((S\NP)/N)/Num, λ {num: Number => λ {a: Attribute => λ {t: TargetObject => ModifyAttribute(t, a, Minus(num))}}}) // Y loses X (attribute).
439450
)) +
440451
("more" -> Seq(
@@ -476,7 +487,8 @@ object Lexicon {
476487
(("object".s :+ "objects '") -> (N, AllObjects: Sem)) +
477488
("odd" -> (NP/N, λ {attr: Attribute => AttributeComparison(attr, IsOdd)})) +
478489
("of" -> Seq(
479-
((S/NP)\V, λ {ops: Seq[AttributeOperation] => λ {t: TargetObject => MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}),
490+
((S/NP)\V, λ {ops: Seq[AttributeOperation] => λ {t: TargetObject =>
491+
validatingSeq(ops, "AttributeOperation") { MultipleActions(Seq(SaveTarget(t)) ++ ops.map(op => ModifyAttribute(SavedTargetObject, op.attr, op.op)))}}}),
480492
((NP/NP)\Num, λ {num: Number => λ {o: ObjectCollection => ChooseO(o, num)}}) // e.g. "X of your opponent's robots"
481493
)) +
482494
("other" -> Seq(
@@ -517,7 +529,7 @@ object Lexicon {
517529
("remove all abilities" -> (S/PP, λ {t: TargetObject => RemoveAllAbilities(t)})) +
518530
("replace" -> Seq(
519531
((S/PP)/NP, λ {r: TextReplacement => λ {t: TargetCard => RewriteText(t, Map(r.from.text -> r.to.text))}}),
520-
((S/PP)/NP, λ {rs: Seq[TextReplacement] => λ {t: TargetCard => RewriteText(t, rs.map(r => (r.from.text -> r.to.text)).toMap)}})
532+
((S/PP)/NP, λ {rs: Seq[TextReplacement] => λ {t: TargetCard => validatingSeq(rs, "TextReplacement") { RewriteText(t, rs.map(r => (r.from.text -> r.to.text)).toMap)}}})
521533
)) +
522534
("restore" -> Seq(
523535
((S/PP)/N, λ {a: Attribute => λ {t: TargetObjectOrPlayer => RestoreAttribute(t, a, None)}}), // e.g. "Restore health to X"
@@ -541,7 +553,7 @@ object Lexicon {
541553
(NP/PP, λ {d: DiscardPile => CardsInDiscardPile(d.player, Robot)}),
542554
((NP/PP)/Adj, λ {condition: CardCondition => λ {hand: Hand => CardsInHand(hand.player, Robot, Seq(condition))}}),
543555
((NP/PP)/Adj, λ {condition: CardCondition => λ {d: DiscardPile => CardsInDiscardPile(d.player, Robot, Seq(condition))}}),
544-
(NP\Adj, λ {attrs: Seq[AttributeAmount] => GeneratedCard(Robot, attrs)}) // e.g. "a 3/1/2 robot"
556+
(NP\Adj, λ {attrs: Seq[AttributeAmount] => validatingSeq(attrs, "AttributeAmount") { GeneratedCard(Robot, attrs)}}) // e.g. "a 3/1/2 robot"
545557
)) +
546558
("robot on the board" -> (N, Robot: Sem)) + // e.g. "If you control a robot on the board with 3 or more health, ..."
547559
("rounded down" -> (Adv, RoundedDown: Sem)) +
@@ -651,11 +663,11 @@ object Lexicon {
651663
(("win".s ++ Seq("win the game", "wins the game")) -> ((S\NP, λ {p: TargetPlayer => WinGame(p)}))) +
652664
("with" -> Seq( // "with" = "that" + "has"
653665
(Adj/NP, identity),
654-
(ReverseConj, λ {a: ParseNode => λ {b: ParseNode => Seq(a, b)}}),
666+
(ReverseConj, λ {a: ParseNode => λ {b: ParseNode => Seq(a, b)}}), // i.e. "[Swap the positions of] a robot WITH your kernel"
655667
((NP\N)/NP, λ {s: AttributeComparison => λ {o: ObjectType => ObjectsMatchingConditions(o, Seq(s))}}),
656-
((NP\N)/NP, λ {s: Seq[AttributeComparison] => λ {o: ObjectType => ObjectsMatchingConditions(o, s)}}),
668+
((NP\N)/NP, λ {cs: Seq[AttributeComparison] => λ {o: ObjectType => validatingSeq(cs, "AttributeComparison") { ObjectsMatchingConditions(o, cs) }}}),
657669
((NP\N)/N, λ {attr: AttributeAmount => λ { o: ObjectType => GeneratedCard(o, Seq(attr))}}), // (generated card with 1 attribute, useful only for structures)
658-
((NP\N)/N, λ {attrs: Seq[AttributeAmount] => λ {o: ObjectType => GeneratedCard(o, attrs)}}),
670+
((NP\N)/N, λ {attrs: Seq[AttributeAmount] => λ {o: ObjectType => validatingSeq(attrs, "AttributeAmount") { GeneratedCard(o, attrs)}}}),
659671
((NP\S)/S, λ {toText: Text => λ {fromText: Text => TextReplacement(fromText, toText)}}) // i.e. "Replace \"<from>\" with \"<to>\" on ..."
660672
)) +
661673
("within" -> Seq(

0 commit comments

Comments
 (0)