oo.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. /**
  2. * OOP Implementation: Class & Interface
  3. *
  4. * In this language, the term 'object' does not have to mean
  5. * an instance of a class, and anything manipulatable is called an 'object'.
  6. * An instance of a class is called an 'Instance Object' or 'Instance',
  7. * which is just one kind of 'object'.
  8. * Note that instance objects in this language are fully encapsulated,
  9. * calling public methods is the only way to manipulate internal data of
  10. * instance objects.
  11. */
  12. pour(Types, {
  13. Class: $(x => x instanceof Class),
  14. Instance: $(x => x instanceof Instance),
  15. Interface: $(x => x instanceof Interface),
  16. })
  17. let OO_Abstract = Uni(Types.Class, Types.Interface)
  18. let ProtoTable = TypedHash.of(TypedList.of(Prototype))
  19. let RawTable = TypedList.of(format({
  20. name: Types.String,
  21. f: Uni(Types.Function, Prototype)
  22. }))
  23. let only_class = x => filter(x, y => is(y, Types.Class))
  24. let only_interface = x => filter(x, y => is(y, Types.Interface))
  25. function build_method_table (raw_table) {
  26. assert(is(raw_table, RawTable))
  27. let reduced = {}
  28. foreach(raw_table, item => {
  29. let { name, f } = item
  30. if (!is(f, Types.Function)) {
  31. return
  32. }
  33. if (!has(name, reduced)) {
  34. reduced[name] = [f]
  35. } else {
  36. reduced[name].push(f)
  37. }
  38. })
  39. return mapval(reduced, (f_list, name) => overload(f_list, name))
  40. }
  41. function build_proto_table (raw_table) {
  42. assert(is(raw_table, RawTable))
  43. let proto_table = {}
  44. foreach(raw_table, item => {
  45. if (is(item.f, Types.Function)) {
  46. return
  47. }
  48. if (!has(item.name, proto_table)) {
  49. proto_table[item.name] = [item.f]
  50. } else {
  51. proto_table[item.name].push(item.f)
  52. }
  53. })
  54. return proto_table
  55. }
  56. /**
  57. * Class Object
  58. */
  59. class Class {
  60. constructor (
  61. name, impls, init, creators, pfs, methods,
  62. ops = {}, data = {}, def_point = null
  63. ) {
  64. // class NAME
  65. assert(is(name, Types.String))
  66. // is IMPLS {
  67. assert(is(impls, TypedList.of(OO_Abstract)))
  68. // init (...) { ... }
  69. assert(is(init, Types.Function))
  70. // create (...) { ... } create (...) { ... } ...
  71. assert(is(creators, TypedList.of(Types.Function)))
  72. // private PF1 (...) { ... } private PF2 (...) { ... } ...
  73. assert(is(pfs, TypedHash.of(Types.Overload)))
  74. // METHOD1 (...) { ... } METHOD2 (...) { ... } ...
  75. assert(is(methods, TypedHash.of(Types.Overload)))
  76. // operator OPERATOR1 (..) { ... } operator OPERATOR2 (..) { ... } ...
  77. assert(is(ops, TypedHash.of(Types.Function)))
  78. // data { ... } }
  79. assert(is(data, Types.Hash))
  80. this.name = name
  81. if (def_point !== null) {
  82. let { file, row, col } = def_point
  83. this.desc = `class ${name} at ${file} (row ${row}, column ${col})`
  84. } else {
  85. this.desc = `class ${name} (built-in)`
  86. }
  87. this.init = init
  88. this.creators = Object.freeze(copy(creators))
  89. this.impls = Object.freeze(copy(impls))
  90. this.pfs = Object.freeze(copy(pfs))
  91. this.methods = Object.freeze(copy(methods))
  92. this.ops = Object.freeze(copy(ops))
  93. this.data = Object.freeze(copy(data))
  94. this.methods_info = get_methods_info(this)
  95. this.operators_info = get_operators_info(this)
  96. this.super_classes = get_super_classes(this)
  97. this.super_interfaces = get_super_interfaces(this)
  98. this.create = get_integrated_constructor(this)
  99. this[Checker] = (object => {
  100. if (!is(object, Types.Instance)) { return false }
  101. return exists(object.class_.super_classes, S => S === this)
  102. })
  103. Object.freeze(this)
  104. }
  105. defined_operator (name) {
  106. return has(name, this.operators_info)
  107. }
  108. get_operator (name) {
  109. assert(this.defined_operator(name))
  110. return this.operators_info[name].f
  111. }
  112. has (key) {
  113. return has(key, this.data)
  114. }
  115. get (key) {
  116. assert(this.has(key))
  117. return this.data[key]
  118. }
  119. get [Symbol.toStringTag]() {
  120. return 'Class'
  121. }
  122. }
  123. function create_class (
  124. name, impls, init, raw_pfs, raw_methods,
  125. options, def_point
  126. ) {
  127. let [ init_main, creators ] = init
  128. let { ops, data } = options
  129. check_impls(impls)
  130. let pfs = build_method_table(raw_pfs)
  131. let methods = build_method_table(raw_methods)
  132. return new Class (
  133. name, impls, init_main, creators, pfs, methods,
  134. ops, data, def_point
  135. )
  136. }
  137. function check_impls (impls) {
  138. assert(is(impls, Types.List))
  139. foreach(impls, (superset, i) => {
  140. ensure(is(superset, OO_Abstract), 'superset_invalid', i)
  141. })
  142. }
  143. /**
  144. * Instance Object
  145. */
  146. class Instance {
  147. constructor (class_, scope) {
  148. assert(is(class_, Types.Class))
  149. assert(scope instanceof Scope)
  150. let mounted_classes = new Set()
  151. let methods = mapval(class_.methods, f => bind_context(f, scope))
  152. let init_finished = false
  153. this.iterate_methods = f => {
  154. foreach(methods, (name, method) => f(name, method))
  155. }
  156. this.has_method = name => {
  157. return has(name, methods)
  158. }
  159. this.get_method = name => {
  160. assert(has(name, methods))
  161. return methods[name]
  162. }
  163. this.mount = another => {
  164. assert(!init_finished)
  165. ensure(is(another, Types.Instance), 'mounting_non_instance')
  166. let declared = exists(class_.impls, I => I === another.class_)
  167. ensure(declared, 'mounting_undeclared', another.class_.desc)
  168. let not_mounted = !mounted_classes.has(another.class_)
  169. ensure(not_mounted, 'mounting_mounted')
  170. another.iterate_methods((name, method) => {
  171. assert(!has(name, methods))
  172. methods[name] = method
  173. })
  174. mounted_classes.add(another.class_)
  175. }
  176. this.finish_init = another => {
  177. assert(!init_finished)
  178. foreach(only_class(class_.impls), C => {
  179. ensure(mounted_classes.has(C), 'not_mounting', C)
  180. })
  181. foreach(only_interface(class_.impls), I => {
  182. apply_implemented(I, this, (name, method) => {
  183. assert(is(name, Types.String))
  184. assert(is(method, Types.Binding))
  185. assert(!has(name, methods))
  186. methods[name] = method
  187. })
  188. })
  189. init_finished = true
  190. }
  191. this.class_ = class_
  192. this.scope = scope
  193. Object.freeze(this)
  194. }
  195. get [Symbol.toStringTag]() {
  196. return 'Instance'
  197. }
  198. }
  199. /**
  200. * Creates a wrapped initializer that returns an instance of the given class
  201. *
  202. * @param class_ Class
  203. * @return Function
  204. */
  205. function wrap_initializer (class_) {
  206. let init = class_.init[WrapperInfo]
  207. return wrap(init.context, init.proto, init.desc, scope => {
  208. // create an instance object using the scope of initializer
  209. let self = new Instance(class_, scope)
  210. // inject private functions
  211. foreach(class_.pfs, (name, pf) => {
  212. ensure(!scope.has(name), 'pf_conflict', name)
  213. scope.declare(name, embrace_in_context(pf, scope))
  214. })
  215. // create mount() for this instance
  216. let mount = another => {
  217. self.mount(another)
  218. return another
  219. }
  220. inject_desc(mount, 'mount')
  221. scope.define_mount(mount)
  222. // invoke the initializer
  223. init.raw(scope)
  224. // do some necessary work
  225. self.finish_init()
  226. // inject self reference
  227. ensure(!scope.has('self'), 'self_conflict')
  228. scope.declare('self', self)
  229. // return the initialized instance
  230. return self
  231. })
  232. }
  233. /**
  234. * Integrate the main initializer and alternative creators of a class
  235. *
  236. * @param class_ Class
  237. * @return Overload
  238. */
  239. function get_integrated_constructor (class_) {
  240. let init = wrap_initializer(class_)
  241. let creators = class_.creators.map(creator => {
  242. creator = creator[WrapperInfo]
  243. return wrap(creator.context, creator.proto, creator.desc, scope => {
  244. let created = creator.raw(scope)
  245. ensure(is(created, class_), 'creator_returned_invalid')
  246. return created
  247. })
  248. })
  249. return overload([...creators, init], class_.name)
  250. }
  251. /**
  252. * Collects all methods of the given class and performs conflict check
  253. *
  254. * @param class_ Class
  255. * @return object
  256. */
  257. function get_methods_info (class_) {
  258. assert(is(class_, Types.Class))
  259. // info format: { NAME -> { method: Binding, from: OO_Abstract } }
  260. let info = {}
  261. // add own methods
  262. foreach(class_.methods, (name, method) => {
  263. info[name] = { method: method, from: class_ }
  264. })
  265. // add mounted methods
  266. foreach(only_class(class_.impls), super_class => {
  267. foreach(super_class.methods_info, (name, method_info) => {
  268. let ok = !has(name, info)
  269. ensure (
  270. ok, 'method_conflict',
  271. name, ok || info[name].from.desc, super_class.desc
  272. )
  273. info[name] = copy(method_info)
  274. })
  275. })
  276. foreach(only_interface(class_.impls), I => {
  277. // add interface (pre-implemented) methods
  278. foreach(I.implemented, (name, method) => {
  279. if (!has(name, info)) {
  280. // if there is no existing method with this name, apply default
  281. info[name] = { method: method, from: I }
  282. } else {
  283. // if such a method exists, it cannot be from another interface
  284. let from_class = is(info[name].from, Types.Class)
  285. ensure (
  286. from_class, 'method_conflict',
  287. name, info[name].from.desc, I.desc
  288. )
  289. }
  290. })
  291. // check if implements the interface I
  292. foreach(I.proto_table, (name, protos) => {
  293. ensure (
  294. has(name, info), 'method_missing',
  295. name, class_.desc, I.desc
  296. )
  297. ensure (
  298. match_protos(info[name].method, protos), 'method_invalid',
  299. name, class_.desc, I.desc
  300. )
  301. })
  302. })
  303. // output the final info
  304. Object.freeze(info)
  305. return info
  306. }
  307. /**
  308. * Collects all operators defined on the class and performs conflict check
  309. *
  310. * @param class_ Class
  311. * @return object
  312. */
  313. function get_operators_info (class_) {
  314. // info format: { NAME -> { f: Function, from: Class } }
  315. let info = {}
  316. foreach(class_.ops, (op, f) => {
  317. info[op] = { f, from: class_ }
  318. })
  319. foreach(only_class(class_.impls), super_class => {
  320. foreach(super_class.operators_info, (op, super_info) => {
  321. let ok = !has(op, info)
  322. ensure (
  323. ok, 'operator_conflict',
  324. op, ok || info[op].from.desc, super_class.desc
  325. )
  326. info[op] = copy(super_info)
  327. })
  328. })
  329. Object.freeze(info)
  330. return info
  331. }
  332. /**
  333. * Tries to get a common operator `op` defined on both `a` and `b`
  334. *
  335. * @param a Instance
  336. * @param b Instance
  337. * @param op string
  338. * @return Function
  339. */
  340. function get_common_operator (a, b, op) {
  341. assert(is(a, Types.Instance))
  342. assert(is(b, Types.Instance))
  343. assert(is(op, Types.String))
  344. let A = a.class_
  345. let B = b.class_
  346. let X = A.operators_info[op].from
  347. let Y = B.operators_info[op].from
  348. ensure(X === Y, 'no_common_class', op)
  349. return A.operators_info[op].f
  350. }
  351. /**
  352. * Collects all [ S ∈ Class | C ⊂ S ] in which C is the argument class_
  353. *
  354. * @param class_ Class
  355. * @return array of Class
  356. */
  357. function get_super_classes (class_) {
  358. let all = list(cat([class_], flat(map(
  359. only_class(class_.impls),
  360. super_class => super_class.super_classes
  361. ))))
  362. Object.freeze(all)
  363. return all
  364. }
  365. /**
  366. * Collects all [ I ∈ Interface | C ⊂ I ] in which C is the argument class_
  367. *
  368. * @param class_ Class
  369. * @return array of Interface
  370. */
  371. function get_super_interfaces (class_) {
  372. let all = list(flat(map(
  373. class_.impls,
  374. S => is(S, Types.Class)? S.super_interfaces: [S]
  375. )))
  376. Object.freeze(all)
  377. return all
  378. }
  379. /**
  380. * Interface Object
  381. */
  382. class Interface {
  383. constructor (name, proto_table, implemented = {}, def_point = null) {
  384. assert(is(name, Types.String))
  385. assert(is(proto_table, ProtoTable))
  386. assert(is(implemented, TypedHash.of(Types.Overload)))
  387. this.name = name
  388. if (def_point !== null) {
  389. let { file, row, col } = def_point
  390. let pos = `${file} (row ${row}, column ${col})`
  391. this.desc = `interface ${name} at ${pos}`
  392. } else {
  393. this.desc = `interface ${name} (built-in)`
  394. }
  395. this.proto_table = mapval(proto_table, l => Object.freeze(copy(l)))
  396. this.implemented = copy(implemented)
  397. Object.freeze(this.proto_table)
  398. Object.freeze(this.implemented)
  399. this[Checker] = (object => {
  400. if (!is(object, Types.Instance)) { return false }
  401. return exists(object.class_.super_interfaces, I => I === this)
  402. })
  403. this.Impl = $(object => {
  404. if(!is(object, Types.Class)) { return false }
  405. return exists(object.super_interfaces, I => I === this)
  406. })
  407. Object.freeze(this)
  408. }
  409. get [Symbol.toStringTag]() {
  410. return 'Interface'
  411. }
  412. }
  413. function create_interface (name, raw_table, def_point) {
  414. let proto_table = build_proto_table(raw_table)
  415. let implemented = build_method_table(raw_table)
  416. validate_interface(proto_table, implemented)
  417. return new Interface(name, proto_table, implemented, def_point)
  418. }
  419. function validate_interface (blank, implemented) {
  420. for (let method of Object.keys(blank)) {
  421. ensure(!has(method, implemented), 'interface_invalid', method)
  422. }
  423. }
  424. /**
  425. * Adds implemented methods of an interface to the given instance
  426. *
  427. * @param interface_ Interface
  428. * @param instance Instance
  429. * @param add_method function
  430. */
  431. function apply_implemented (interface_, instance, add_method) {
  432. let implemented = interface_.implemented
  433. let keys = Object.keys(implemented)
  434. if (keys.length == 0) { return }
  435. let sample = implemented[keys[0]]
  436. let context = sample[WrapperInfo].functions[0][WrapperInfo].context
  437. // create the context scope for implemented methods
  438. let interface_scope = new Scope(context)
  439. // add blank methods to the scope
  440. foreach(interface_.proto_table, (name, _) => {
  441. assert(instance.has_method(name))
  442. interface_scope.declare(name, instance.get_method(name))
  443. })
  444. // for each implemented method
  445. foreach(implemented, (name, method) => {
  446. assert(!has(name, interface_.proto_table))
  447. // create a binding
  448. let binding = bind_context(method, interface_scope)
  449. // add to the context scope
  450. interface_scope.declare(name, binding)
  451. // add to the instance
  452. add_method(name, binding)
  453. })
  454. }
  455. /**
  456. * Checks if two function prototypes are equivalent
  457. *
  458. * @param proto1 Prototype
  459. * @param proto2 Prototype
  460. * @return boolean
  461. */
  462. function proto_equal (proto1, proto2) {
  463. assert(is(proto1, Prototype))
  464. assert(is(proto2, Prototype))
  465. if (proto1.parameters.length != proto2.parameters.length) {
  466. return false
  467. }
  468. if (!type_equivalent(proto1.value_type, proto2.value_type)) {
  469. return false
  470. }
  471. return equal(proto1.parameters, proto2.parameters, (p1, p2) => {
  472. return type_equivalent(p1.type, p2.type)
  473. })
  474. }
  475. /**
  476. * Checks if a method matches the given function prototypes
  477. *
  478. * @param method Wrapped
  479. * @param protos array of Prototype
  480. */
  481. function match_protos (method, protos) {
  482. assert(is(method, Types.Wrapped))
  483. assert(is(protos, TypedList.of(Prototype)))
  484. assert(protos.length > 0)
  485. method = cancel_binding(method)
  486. let method_protos = []
  487. if (is(method, Types.Function)) {
  488. method_protos = [method[WrapperInfo].proto]
  489. } else {
  490. assert(is(method, Types.Overload))
  491. method_protos = method[WrapperInfo].functions.map(
  492. f => f[WrapperInfo].proto
  493. )
  494. }
  495. // ∃ p ∈ method_protos, ∃ q ∈ protos, such that p is equivalent to q
  496. return exists(method_protos, p => exists(protos, q => proto_equal(p, q)))
  497. }
  498. /**
  499. * Calls a method through OO or UFCS
  500. *
  501. * @param caller_scope Scope | null
  502. * @param object any
  503. * @param method_name string
  504. * @param args array
  505. * @param file string
  506. * @param row integer
  507. * @param col integer
  508. * @return any
  509. */
  510. function call_method (
  511. caller_scope, object, method_name, args, file = null, row = -1, col = -1
  512. ) {
  513. assert(caller_scope instanceof Scope || caller_scope === null)
  514. assert(is(method_name, Types.String))
  515. assert(is(args, Types.List))
  516. // OO: find the method on the instance object
  517. if (is(object, Types.Instance)) {
  518. if (object.has_method(method_name)) {
  519. return call(object.get_method(method_name), args, file, row, col)
  520. }
  521. }
  522. if (is(object, Types.ES_Object)) {
  523. let method = object[method_name]
  524. if (is(method, ES.Function)) {
  525. return call(method.bind(object), args, file, row, col)
  526. }
  527. }
  528. // UFCS: find the method in the caller scope
  529. ensure(caller_scope !== null, 'method_not_found', method_name)
  530. let result = caller_scope.find_function(method_name)
  531. if (result.value === NotFound || !result.ok) {
  532. let point_desc = `${file} (row ${row}, column ${col})`
  533. if (result.value === NotFound) {
  534. ensure(false, 'method_not_found', method_name, point_desc)
  535. }
  536. if (!result.ok) {
  537. ensure(false, 'variable_inconsistent', method_name, point_desc)
  538. }
  539. }
  540. let method = result.value
  541. assert(is(method, ES.Function))
  542. return call(method, [object, ...args], file, row, col)
  543. }