-
Notifications
You must be signed in to change notification settings - Fork 0
/
STLC.scala
180 lines (133 loc) · 4.91 KB
/
STLC.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
object STLC {
// HELPER FUNCTIONS
def ??? = throw new Exception("???")
def typeErrorNotTheSame(context: String, expected: Any, actual: Any) =
typeError("expected %s instead of %s in %s".format(expected, actual, context))
def typeErrorNotDefined(name : Name) =
typeError("undefined identifier " + name)
def typeError(msg: String) = throw new Exception("Type error: " + msg)
// TYPES
trait Type
case object NumberType extends Type
case class FunctionType(domain: Type, range: Type) extends Type
case class MapType(domain: Type, range: Type) extends Type
// TYPED VARIABLES
type Name = String
case class Variable(name: Name, _type: Type)
// TERMS
trait Typed {
/** Returns type or fails horribly. */
def _type : Type
}
trait Constant extends Typed
case class LookupConstant(domain : Type, range : Type) extends Constant {
override def _type : Type = FunctionType(MapType(domain, range), FunctionType(domain, range))
}
trait Term extends Typed {
// forces early failure for object-level type checking
_type
}
case class VarTerm(variable: Variable) extends Term {
override def _type = variable._type
}
case class AppTerm(operator: Term, operand: Term) extends Term {
override def _type = (operator._type, operand._type) match {
case (FunctionType(expectedDomain, range), actualDomain) =>
if (expectedDomain == actualDomain)
range
else
typeErrorNotTheSame("operand position", expectedDomain, operand + " : " + actualDomain)
case _ => typeErrorNotTheSame("operator position", "function", operator._type)
}
}
case class AbsTerm(variable: Variable, body: Term) extends Term {
override def _type = FunctionType(variable._type, body._type)
}
case class ConstTerm(constant : Constant) extends Term {
override def _type = constant._type
}
// TYPING CONTEXTS
type TypingContext = List[Variable]
def lookup(context: TypingContext, name: Name) : Variable =
context filter (_.name == name) match {
case (result :: _) => result
case _ => typeErrorNotDefined(name)
}
def insert(variable: Variable, context: TypingContext) : TypingContext =
variable :: context
// SMART CONSTRUCTORS
type Term2 = TypingContext => Term
def appTerm2(operator: Term2, operand: Term2): Term2 =
context =>
AppTerm(operator(context), operand(context))
def absTerm2(variable: Variable, body: Term2): Term2 =
context =>
AbsTerm(variable, body(insert(variable, context)))
def varTerm2(name: Name): Term2 =
context =>
VarTerm(lookup(context, name))
def lookupTerm2(map: Term2, key: Term2): Term2 =
context => {
val typedMap = map(context)
val typedKey = key(context)
typedMap._type match {
case MapType(keyType, valType) =>
AppTerm(AppTerm(ConstTerm(LookupConstant(keyType, valType)),
typedMap),
typedKey)
case _ =>
typeErrorNotTheSame("use of lookup", "map", typedMap._type)
}
}
// Hack for testing
case object Type1 extends Type
case object Type2 extends Type
// should and does succeed:
val test1 =
absTerm2(Variable("theMap", MapType(Type1, Type1)),
absTerm2(Variable("theKey", Type1),
lookupTerm2(varTerm2("theMap"), varTerm2("theKey"))))
val test2 =
absTerm2(Variable("theMap", MapType(Type1, Type2)),
absTerm2(Variable("theKey", Type1),
lookupTerm2(varTerm2("theMap"), varTerm2("theKey"))))
// should fail and does fail:
val test3 =
absTerm2(Variable("theMap", MapType(Type1, Type1)),
absTerm2(Variable("notTheKey", Type1),
lookupTerm2(varTerm2("theMap"), varTerm2("theKey"))))
val test4 =
absTerm2(Variable("theMap", MapType(Type2, Type2)),
absTerm2(Variable("theKey", Type1),
lookupTerm2(varTerm2("theMap"), varTerm2("theKey"))))
val test5 =
absTerm2(Variable("theMap", MapType(Type1, Type2)),
absTerm2(Variable("theKey", Type2),
lookupTerm2(varTerm2("theMap"), varTerm2("theKey"))))
val test6 =
absTerm2(Variable("theMap", MapType(Type1, Type1)),
absTerm2(Variable("theKey", Type1),
lookupTerm2(varTerm2("theMap"), varTerm2("theMap"))))
// HOAS INTERFACE :)
object fresh extends Function2[TypingContext, Name, Name]{
var index = 0;
def apply(context : TypingContext, prefix : Name) : Name = {
val result = prefix + index
index += 1
result
}
}
def absTerm3(domain : Type, body: Term2 => Term2): Term2 =
context => {
val name = fresh(context, "x")
val variable = Variable(name, domain)
AbsTerm(variable, body(_ => VarTerm(variable))(insert(variable, context)))
}
}
import STLC._
test1(List())
test2(List())
test3(List())
test4(List())
test5(List())
test6(List())