base.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. ;;; ECMAScript for Guile
  2. ;; Copyright (C) 2009, 2013, 2015 Free Software Foundation, Inc.
  3. ;;;; This library is free software; you can redistribute it and/or
  4. ;;;; modify it under the terms of the GNU Lesser General Public
  5. ;;;; License as published by the Free Software Foundation; either
  6. ;;;; version 3 of the License, or (at your option) any later version.
  7. ;;;;
  8. ;;;; This library is distributed in the hope that it will be useful,
  9. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;;;; Lesser General Public License for more details.
  12. ;;;;
  13. ;;;; You should have received a copy of the GNU Lesser General Public
  14. ;;;; License along with this library; if not, write to the Free Software
  15. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16. ;;; Code:
  17. (define-module (language ecmascript base)
  18. #:use-module (oop goops)
  19. #:export (*undefined* *this*
  20. <js-object> *object-prototype*
  21. js-prototype js-props js-prop-attrs js-value js-constructor js-class
  22. pget prop-keys prop-attrs prop-has-attr? pput has-property? pdel
  23. object->string object->number object->value/string
  24. object->value/number object->value
  25. ->primitive ->boolean ->number ->integer ->int32 ->uint32
  26. ->uint16 ->string ->object
  27. call/this* call/this lambda/this define-js-method
  28. new-object new))
  29. (define-class <undefined> ())
  30. (define *undefined* (make <undefined>))
  31. (define *this* (make-fluid))
  32. (define-class <js-object> ()
  33. (prototype #:getter js-prototype #:init-keyword #:prototype
  34. #:init-thunk (lambda () *object-prototype*))
  35. (props #:getter js-props #:init-form (make-hash-table 7))
  36. (prop-attrs #:getter js-prop-attrs #:init-value #f)
  37. (value #:getter js-value #:init-value #f #:init-keyword #:value)
  38. (constructor #:getter js-constructor #:init-value #f #:init-keyword #:constructor)
  39. (class #:getter js-class #:init-value "Object" #:init-keyword #:class))
  40. (define-method (prop-keys (o <js-object>))
  41. (hash-map->list (lambda (k v) k) (js-props o)))
  42. (define-method (pget (o <js-object>) (p <string>))
  43. (pget o (string->symbol p)))
  44. (define-method (pget (o <js-object>) p)
  45. (let ((h (hashq-get-handle (js-props o) p)))
  46. (if h
  47. (cdr h)
  48. (let ((proto (js-prototype o)))
  49. (if proto
  50. (pget proto p)
  51. *undefined*)))))
  52. (define-method (prop-attrs (o <js-object>) p)
  53. (or (let ((attrs (js-prop-attrs o)))
  54. (and attrs (hashq-ref (js-prop-attrs o) p)))
  55. (let ((proto (js-prototype o)))
  56. (if proto
  57. (prop-attrs proto p)
  58. '()))))
  59. (define-method (prop-has-attr? (o <js-object>) p attr)
  60. (memq attr (prop-attrs o p)))
  61. (define-method (pput (o <js-object>) p v)
  62. (if (prop-has-attr? o p 'ReadOnly)
  63. (throw 'ReferenceError o p)
  64. (hashq-set! (js-props o) p v)))
  65. (define-method (pput (o <js-object>) (p <string>) v)
  66. (pput o (string->symbol p) v))
  67. (define-method (pdel (o <js-object>) p)
  68. (if (prop-has-attr? o p 'DontDelete)
  69. #f
  70. (begin
  71. (pput o p *undefined*)
  72. #t)))
  73. (define-method (pdel (o <js-object>) (p <string>) v)
  74. (pdel o (string->symbol p)))
  75. (define-method (has-property? (o <js-object>) p)
  76. (if (hashq-get-handle (js-props o) p)
  77. #t
  78. (let ((proto (js-prototype o)))
  79. (if proto
  80. (has-property? proto p)
  81. #f))))
  82. (define (call/this* this f)
  83. (with-fluid* *this* this f))
  84. (define-macro (call/this this f . args)
  85. `(with-fluid* *this* ,this (lambda () (,f . ,args))))
  86. (define-macro (lambda/this formals . body)
  87. `(lambda ,formals (let ((this (fluid-ref *this*))) . ,body)))
  88. (define-macro (define-js-method object name-and-args . body)
  89. `(pput ,object ',(car name-and-args) (lambda/this ,(cdr name-and-args) . ,body)))
  90. (define *object-prototype* #f)
  91. (set! *object-prototype* (make <js-object>))
  92. (define-js-method *object-prototype* (toString)
  93. (format #f "[object ~A]" (js-class this)))
  94. (define-js-method *object-prototype* (toLocaleString . args)
  95. ((pget *object-prototype* 'toString)))
  96. (define-js-method *object-prototype* (valueOf)
  97. this)
  98. (define-js-method *object-prototype* (hasOwnProperty p)
  99. (and (hashq-get-handle (js-props this) p) #t))
  100. (define-js-method *object-prototype* (isPrototypeOf v)
  101. (eq? this (js-prototype v)))
  102. (define-js-method *object-prototype* (propertyIsEnumerable p)
  103. (and (hashq-get-handle (js-props this) p)
  104. (not (prop-has-attr? this p 'DontEnum))))
  105. (define (object->string o error?)
  106. (let ((toString (pget o 'toString)))
  107. (if (procedure? toString)
  108. (let ((x (call/this o toString)))
  109. (if (and error? (is-a? x <js-object>))
  110. (throw 'TypeError o 'default-value)
  111. x))
  112. (if error?
  113. (throw 'TypeError o 'default-value)
  114. o))))
  115. (define (object->number o error?)
  116. (let ((valueOf (pget o 'valueOf)))
  117. (if (procedure? valueOf)
  118. (let ((x (call/this o valueOf)))
  119. (if (and error? (is-a? x <js-object>))
  120. (throw 'TypeError o 'default-value)
  121. x))
  122. (if error?
  123. (throw 'TypeError o 'default-value)
  124. o))))
  125. (define (object->value/string o)
  126. (if (is-a? o <js-object>)
  127. (object->number o #t)
  128. o))
  129. (define (object->value/number o)
  130. (if (is-a? o <js-object>)
  131. (object->string o #t)
  132. o))
  133. (define (object->value o)
  134. ;; FIXME: if it's a date, we should try numbers first
  135. (object->value/string o))
  136. (define (->primitive x)
  137. (if (is-a? x <js-object>)
  138. (object->value x)
  139. x))
  140. (define (->boolean x)
  141. (not (or (not x) (null? x) (eq? x *undefined*)
  142. (and (number? x) (or (zero? x) (nan? x)))
  143. (and (string? x) (= (string-length x) 0)))))
  144. (define (->number x)
  145. (cond ((number? x) x)
  146. ((boolean? x) (if x 1 0))
  147. ((null? x) 0)
  148. ((eq? x *undefined*) +nan.0)
  149. ((is-a? x <js-object>) (object->number x #t))
  150. ((string? x) (string->number x))
  151. (else (throw 'TypeError x '->number))))
  152. (define (->integer x)
  153. (let ((n (->number x)))
  154. (cond ((nan? n) 0)
  155. ((zero? n) n)
  156. ((inf? n) n)
  157. (else (inexact->exact (round n))))))
  158. (define (->int32 x)
  159. (let ((n (->number x)))
  160. (if (or (nan? n) (zero? n) (inf? n))
  161. 0
  162. (let ((m (logand (1- (ash 1 32)) (inexact->exact (round n)))))
  163. (if (negative? n)
  164. (- m (ash 1 32))
  165. m)))))
  166. (define (->uint32 x)
  167. (let ((n (->number x)))
  168. (if (or (nan? n) (zero? n) (inf? n))
  169. 0
  170. (logand (1- (ash 1 32)) (inexact->exact (round n))))))
  171. (define (->uint16 x)
  172. (let ((n (->number x)))
  173. (if (or (nan? n) (zero? n) (inf? n))
  174. 0
  175. (logand (1- (ash 1 16)) (inexact->exact (round n))))))
  176. (define (->string x)
  177. (cond ((eq? x *undefined*) "undefined")
  178. ((null? x) "null")
  179. ((boolean? x) (if x "true" "false"))
  180. ((string? x) x)
  181. ((number? x)
  182. (cond ((nan? x) "NaN")
  183. ((zero? x) "0")
  184. ((inf? x) "Infinity")
  185. (else (number->string x))))
  186. (else (->string (object->value/string x)))))
  187. (define (->object x)
  188. (cond ((eq? x *undefined*) (throw 'TypeError x '->object))
  189. ((null? x) (throw 'TypeError x '->object))
  190. ((boolean? x) (make <js-object> #:prototype Boolean #:value x))
  191. ((number? x) (make <js-object> #:prototype String #:value x))
  192. ((string? x) (make <js-object> #:prototype Number #:value x))
  193. (else x)))
  194. (define (new-object . pairs)
  195. (let ((o (make <js-object>)))
  196. (map (lambda (pair)
  197. (pput o (car pair) (cdr pair)))
  198. pairs)
  199. o))
  200. (slot-set! *object-prototype* 'constructor new-object)
  201. (define-method (new o . initargs)
  202. (let ((ctor (js-constructor o)))
  203. (if (not ctor)
  204. (throw 'TypeError 'new o)
  205. (let ((o (make <js-object>
  206. #:prototype (or (js-prototype o) *object-prototype*))))
  207. (let ((new-o (call/this o apply ctor initargs)))
  208. (if (is-a? new-o <js-object>)
  209. new-o
  210. o))))))