base.scm 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. ;;; ECMAScript for Guile
  2. ;; Copyright (C) 2009 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 *undefined* ((@@ (oop goops) make-unbound)))
  30. (define *this* (make-fluid))
  31. (define-class <js-object> ()
  32. (prototype #:getter js-prototype #:init-keyword #:prototype
  33. #:init-thunk (lambda () *object-prototype*))
  34. (props #:getter js-props #:init-form (make-hash-table 7))
  35. (prop-attrs #:getter js-prop-attrs #:init-value #f)
  36. (value #:getter js-value #:init-value #f #:init-keyword #:value)
  37. (constructor #:getter js-constructor #:init-value #f #:init-keyword #:constructor)
  38. (class #:getter js-class #:init-value "Object" #:init-keyword #:class))
  39. (define-method (prop-keys (o <js-object>))
  40. (hash-map->list (lambda (k v) k) (js-props o)))
  41. (define-method (pget (o <js-object>) (p <string>))
  42. (pget o (string->symbol p)))
  43. (define-method (pget (o <js-object>) p)
  44. (let ((h (hashq-get-handle (js-props o) p)))
  45. (if h
  46. (cdr h)
  47. (let ((proto (js-prototype o)))
  48. (if proto
  49. (pget proto p)
  50. *undefined*)))))
  51. (define-method (prop-attrs (o <js-object>) p)
  52. (or (let ((attrs (js-prop-attrs o)))
  53. (and attrs (hashq-ref (js-prop-attrs o) p)))
  54. (let ((proto (js-prototype o)))
  55. (if proto
  56. (prop-attrs proto p)
  57. '()))))
  58. (define-method (prop-has-attr? (o <js-object>) p attr)
  59. (memq attr (prop-attrs o p)))
  60. (define-method (pput (o <js-object>) p v)
  61. (if (prop-has-attr? o p 'ReadOnly)
  62. (throw 'ReferenceError o p)
  63. (hashq-set! (js-props o) p v)))
  64. (define-method (pput (o <js-object>) (p <string>) v)
  65. (pput o (string->symbol p) v))
  66. (define-method (pdel (o <js-object>) p)
  67. (if (prop-has-attr? o p 'DontDelete)
  68. #f
  69. (begin
  70. (pput o p *undefined*)
  71. #t)))
  72. (define-method (pdel (o <js-object>) (p <string>) v)
  73. (pdel o (string->symbol p)))
  74. (define-method (has-property? (o <js-object>) p)
  75. (if (hashq-get-handle (js-props o) v)
  76. #t
  77. (let ((proto (js-prototype o)))
  78. (if proto
  79. (has-property? proto p)
  80. #f))))
  81. (define (call/this* this f)
  82. (with-fluid* *this* this f))
  83. (define-macro (call/this this f . args)
  84. `(with-fluid* *this* ,this (lambda () (,f . ,args))))
  85. (define-macro (lambda/this formals . body)
  86. `(lambda ,formals (let ((this (fluid-ref *this*))) . ,body)))
  87. (define-macro (define-js-method object name-and-args . body)
  88. `(pput ,object ',(car name-and-args) (lambda/this ,(cdr name-and-args) . ,body)))
  89. (define *object-prototype* #f)
  90. (set! *object-prototype* (make <js-object>))
  91. (define-js-method *object-prototype* (toString)
  92. (format #f "[object ~A]" (js-class this)))
  93. (define-js-method *object-prototype* (toLocaleString . args)
  94. ((pget *object-prototype* 'toString)))
  95. (define-js-method *object-prototype* (valueOf)
  96. this)
  97. (define-js-method *object-prototype* (hasOwnProperty p)
  98. (and (hashq-get-handle (js-props this) p) #t))
  99. (define-js-method *object-prototype* (isPrototypeOf v)
  100. (eq? this (js-prototype v)))
  101. (define-js-method *object-prototype* (propertyIsEnumerable p)
  102. (and (hashq-get-handle (js-props this) p)
  103. (not (prop-has-attr? this p 'DontEnum))))
  104. (define (object->string o error?)
  105. (let ((toString (pget o 'toString)))
  106. (if (procedure? toString)
  107. (let ((x (call/this o toString)))
  108. (if (and error? (is-a? x <js-object>))
  109. (throw 'TypeError o 'default-value)
  110. x))
  111. (if error?
  112. (throw 'TypeError o 'default-value)
  113. o))))
  114. (define (object->number o error?)
  115. (let ((valueOf (pget o 'valueOf)))
  116. (if (procedure? valueOf)
  117. (let ((x (call/this o valueOf)))
  118. (if (and error? (is-a? x <js-object>))
  119. (throw 'TypeError o 'default-value)
  120. x))
  121. (if error?
  122. (throw 'TypeError o 'default-value)
  123. o))))
  124. (define (object->value/string o)
  125. (if (is-a? o <js-object>)
  126. (object->number o #t)
  127. o))
  128. (define (object->value/number o)
  129. (if (is-a? o <js-object>)
  130. (object->string o #t)
  131. o))
  132. (define (object->value o)
  133. ;; FIXME: if it's a date, we should try numbers first
  134. (object->value/string o))
  135. (define (->primitive x)
  136. (if (is-a? x <js-object>)
  137. (object->value x)
  138. x))
  139. (define (->boolean x)
  140. (not (or (not x) (null? x) (eq? x *undefined*) (zero? x) (nan? x)
  141. (and (string? x) (= (string-length x) 0)))))
  142. (define (->number x)
  143. (cond ((number? x) x)
  144. ((boolean? x) (if x 1 0))
  145. ((null? x) 0)
  146. ((eq? x *undefined*) +nan.0)
  147. ((is-a? x <js-object>) (object->number x))
  148. ((string? x) (string->number x))
  149. (else (throw 'TypeError o '->number))))
  150. (define (->integer x)
  151. (let ((n (->number x)))
  152. (cond ((nan? n) 0)
  153. ((zero? n) n)
  154. ((inf? n) n)
  155. (else (inexact->exact (round n))))))
  156. (define (->int32 x)
  157. (let ((n (->number x)))
  158. (if (or (nan? n) (zero? n) (inf? n))
  159. 0
  160. (let ((m (logand (1- (ash 1 32)) (inexact->exact (round n)))))
  161. (if (negative? n)
  162. (- m (ash 1 32))
  163. m)))))
  164. (define (->uint32 x)
  165. (let ((n (->number x)))
  166. (if (or (nan? n) (zero? n) (inf? n))
  167. 0
  168. (logand (1- (ash 1 32)) (inexact->exact (round n))))))
  169. (define (->uint16 x)
  170. (let ((n (->number x)))
  171. (if (or (nan? n) (zero? n) (inf? n))
  172. 0
  173. (logand (1- (ash 1 16)) (inexact->exact (round n))))))
  174. (define (->string x)
  175. (cond ((eq? x *undefined*) "undefined")
  176. ((null? x) "null")
  177. ((boolean? x) (if x "true" "false"))
  178. ((string? x) x)
  179. ((number? x)
  180. (cond ((nan? x) "NaN")
  181. ((zero? x) "0")
  182. ((inf? x) "Infinity")
  183. (else (number->string x))))
  184. (else (->string (object->value/string x)))))
  185. (define (->object x)
  186. (cond ((eq? x *undefined*) (throw 'TypeError x '->object))
  187. ((null? x) (throw 'TypeError x '->object))
  188. ((boolean? x) (make <js-object> #:prototype Boolean #:value x))
  189. ((number? x) (make <js-object> #:prototype String #:value x))
  190. ((string? x) (make <js-object> #:prototype Number #:value x))
  191. (else x)))
  192. (define (new-object . pairs)
  193. (let ((o (make <js-object>)))
  194. (map (lambda (pair)
  195. (pput o (car pair) (cdr pair)))
  196. pairs)
  197. o))
  198. (slot-set! *object-prototype* 'constructor new-object)
  199. (define-method (new o . initargs)
  200. (let ((ctor (js-constructor o)))
  201. (if (not ctor)
  202. (throw 'TypeError 'new o)
  203. (let ((o (make <js-object>
  204. #:prototype (or (js-prototype o) *object-prototype*))))
  205. (let ((new-o (call/this o apply ctor initargs)))
  206. (if (is-a? new-o <js-object>)
  207. new-o
  208. o))))))