mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-02 13:00:26 +02:00
add lua language implementation
What is missing: + Functions: module, getfenv, setfenv, math.modf, table.sort + Parser: needs to be more flexible + Compiler: needs more extensive work to properly handle all possible cases of variable arguments, multiple returns, and loops + Language: Variable arguments and unpacking of multiple returns. (For example we need to be able to handle something as complex as print(unpack({...})), which is easy with Lua's explicit stack but will require lots of tree-il gymnastics, or perhaps modifications to better allow different calling conventions. (For instance -- how would we support Python or Ruby, where keyword arguments are gathered into a hashtable and passed as a single argument?) What is there: A fair shot at supporting Lua 5.1, not quite a drop-in replacement, but not far from that goal either.
This commit is contained in:
parent
6871327742
commit
a30c18c22a
16 changed files with 3242 additions and 0 deletions
|
@ -71,6 +71,7 @@ SOURCES = \
|
||||||
$(SCRIPTS_SOURCES) \
|
$(SCRIPTS_SOURCES) \
|
||||||
$(ECMASCRIPT_LANG_SOURCES) \
|
$(ECMASCRIPT_LANG_SOURCES) \
|
||||||
$(ELISP_LANG_SOURCES) \
|
$(ELISP_LANG_SOURCES) \
|
||||||
|
$(LUA_LANG_SOURCES) \
|
||||||
$(BRAINFUCK_LANG_SOURCES) \
|
$(BRAINFUCK_LANG_SOURCES) \
|
||||||
$(LIB_SOURCES) \
|
$(LIB_SOURCES) \
|
||||||
$(WEB_SOURCES)
|
$(WEB_SOURCES)
|
||||||
|
@ -175,6 +176,18 @@ ELISP_LANG_SOURCES = \
|
||||||
language/elisp/runtime/value-slot.scm \
|
language/elisp/runtime/value-slot.scm \
|
||||||
language/elisp/spec.scm
|
language/elisp/spec.scm
|
||||||
|
|
||||||
|
LUA_LANG_SOURCES = \
|
||||||
|
language/lua/common.scm \
|
||||||
|
language/lua/runtime.scm \
|
||||||
|
language/lua/lexer.scm \
|
||||||
|
language/lua/parser.scm \
|
||||||
|
language/lua/compile-tree-il.scm \
|
||||||
|
language/lua/standard/io.scm \
|
||||||
|
language/lua/standard/math.scm \
|
||||||
|
language/lua/standard/os.scm \
|
||||||
|
language/lua/standard/table.scm \
|
||||||
|
language/lua/spec.scm
|
||||||
|
|
||||||
BRAINFUCK_LANG_SOURCES = \
|
BRAINFUCK_LANG_SOURCES = \
|
||||||
language/brainfuck/parse.scm \
|
language/brainfuck/parse.scm \
|
||||||
language/brainfuck/compile-scheme.scm \
|
language/brainfuck/compile-scheme.scm \
|
||||||
|
|
50
module/language/lua/common.scm
Normal file
50
module/language/lua/common.scm
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
;;; Guile Lua --- common lua functionality
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua common)
|
||||||
|
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
|
||||||
|
#:export (syntax-error or-eqv?))
|
||||||
|
|
||||||
|
(define (syntax-error src string . arguments)
|
||||||
|
"Throw an error tagged with 'lua-syntax, and print detailed source
|
||||||
|
code information when available. STRING and ARGUMENTS are given to FORMAT."
|
||||||
|
(throw 'lua-syntax
|
||||||
|
(string-append
|
||||||
|
(if src
|
||||||
|
(format #f "~a@~a.~a"
|
||||||
|
(cdr (assq 'filename src))
|
||||||
|
(cdr (assq 'line src))
|
||||||
|
(if (assq 'column src)
|
||||||
|
(cdr (assq 'column src))
|
||||||
|
"[no column available]"))
|
||||||
|
"[no source code information given]")
|
||||||
|
": "
|
||||||
|
(apply format (cons string arguments)))))
|
||||||
|
|
||||||
|
;; I was using CASE, but this is more succinct
|
||||||
|
;; (or-eqv? 1 #f 1) => (or (eqv? 1 #f) (eqv? 1 1))
|
||||||
|
(define-syntax or-eqv?
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ test '(value ...))
|
||||||
|
(or (eqv? test 'value) ...))
|
||||||
|
((_ test value ...)
|
||||||
|
(or (eqv? test value) ...))))
|
398
module/language/lua/compile-tree-il.scm
Normal file
398
module/language/lua/compile-tree-il.scm
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
;;; Guile Lua --- compiler
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua compile-tree-il)
|
||||||
|
#:use-module (language tree-il)
|
||||||
|
#:use-module ((srfi srfi-1) #:select (map!))
|
||||||
|
#:use-module (srfi srfi-39)
|
||||||
|
#:use-module ((system base syntax) #:select (record-case))
|
||||||
|
#:use-module (rnrs control)
|
||||||
|
|
||||||
|
#:use-module (language lua common)
|
||||||
|
#:use-module (language lua parser)
|
||||||
|
#:use-module (language lua runtime)
|
||||||
|
|
||||||
|
#:export (compile-tree-il))
|
||||||
|
|
||||||
|
;; utilities
|
||||||
|
|
||||||
|
(define *runtime-name* '(language lua runtime))
|
||||||
|
(define no-arguments '(() #f #f #f () ()))
|
||||||
|
|
||||||
|
|
||||||
|
(define (ref-runtime src name)
|
||||||
|
(make-module-ref src *runtime-name* name #t))
|
||||||
|
|
||||||
|
(define (make-runtime-application src name arguments)
|
||||||
|
"Apply a function in the (language lua runtime) module"
|
||||||
|
(make-application src (ref-runtime src name) arguments))
|
||||||
|
|
||||||
|
(define (make-lua-conditional src condition then else)
|
||||||
|
"Generate a conditional with (@ (language lua runtime) true?)"
|
||||||
|
(make-conditional src (make-runtime-application src 'true? (list condition)) then else))
|
||||||
|
|
||||||
|
(define (make-table-ref src table index)
|
||||||
|
(make-runtime-application src 'index
|
||||||
|
(list table (if (symbol? index) (make-const src (symbol->string index)) index))))
|
||||||
|
|
||||||
|
(define (make-table-set! src table index exp)
|
||||||
|
(make-runtime-application src 'new-index!
|
||||||
|
(list table (if (symbol? index) (make-const src (symbol->string index)) index) exp)))
|
||||||
|
|
||||||
|
(define (apply-named-lua-function src name get-body)
|
||||||
|
(let* ((name (gensym (string-append " " name)))
|
||||||
|
(parameters (list name)))
|
||||||
|
(make-application
|
||||||
|
src
|
||||||
|
(make-module-ref src '(guile) 'catch #t)
|
||||||
|
(list
|
||||||
|
(make-const src 'lua-break)
|
||||||
|
(make-argless-lambda src
|
||||||
|
(make-let
|
||||||
|
src
|
||||||
|
parameters parameters
|
||||||
|
(list (make-lambda src '() (get-body name)))
|
||||||
|
(make-application src (make-lexical-ref src name name) '())))
|
||||||
|
(make-arg-ignoring-lambda src
|
||||||
|
(make-void src))))))
|
||||||
|
|
||||||
|
(define (while-loop->tree-il src condition body)
|
||||||
|
"Create a WHILE loop, used by both WHILE and REPEAT."
|
||||||
|
(apply-named-lua-function
|
||||||
|
src "while"
|
||||||
|
(lambda (loop)
|
||||||
|
(make-lua-conditional
|
||||||
|
src
|
||||||
|
condition
|
||||||
|
(make-sequence src
|
||||||
|
(list body (make-application src (make-lexical-ref src loop loop) '())))
|
||||||
|
(make-void src)))))
|
||||||
|
|
||||||
|
;; calling conventions
|
||||||
|
(define (make-plain-lambda-case src args gensyms body . alternate)
|
||||||
|
(make-lambda-case src args #f #f #f '() (or gensyms args) body (and (not (null? alternate)) (car alternate))))
|
||||||
|
|
||||||
|
(define (make-plain-lambda src args gensyms body . alternate)
|
||||||
|
(make-lambda src '() (apply make-plain-lambda-case (append (list src args gensyms body) alternate))))
|
||||||
|
|
||||||
|
(define (make-arg-ignoring-lambda src body)
|
||||||
|
(make-lambda src '() (make-lambda-case src '() #f '_ #f '() (list (gensym "_")) body #f)))
|
||||||
|
|
||||||
|
(define (make-argless-lambda src body)
|
||||||
|
(make-plain-lambda src '() #f body))
|
||||||
|
|
||||||
|
(define (adjust-to-single-value src exp)
|
||||||
|
"adjust an expression so that it only returns one result; the rest are dropped silently"
|
||||||
|
(define value-gensym (gensym "%value"))
|
||||||
|
(define adjust-gensym (gensym "%adjust"))
|
||||||
|
(make-letrec src
|
||||||
|
#t
|
||||||
|
'(%adjust)
|
||||||
|
(list adjust-gensym)
|
||||||
|
(list
|
||||||
|
(make-plain-lambda
|
||||||
|
src
|
||||||
|
'(%value)
|
||||||
|
(list value-gensym)
|
||||||
|
(make-lexical-ref src '%value value-gensym)))
|
||||||
|
(make-application
|
||||||
|
src
|
||||||
|
(make-primitive-ref src 'call-with-values)
|
||||||
|
(list (make-argless-lambda src exp) (make-lexical-ref src '%adjust adjust-gensym)))))
|
||||||
|
|
||||||
|
;; main compiler
|
||||||
|
|
||||||
|
(define context (make-parameter #f))
|
||||||
|
|
||||||
|
(define* (compile exp #:optional last-in-list?)
|
||||||
|
(define* (map-compile exps #:optional care-about-last?)
|
||||||
|
(let lp ((ls exps)
|
||||||
|
(tree '()))
|
||||||
|
(if (null? ls)
|
||||||
|
(reverse! tree)
|
||||||
|
(lp (cdr ls) (cons (compile (car ls) (and care-about-last? (null? (cdr ls)))) tree)))))
|
||||||
|
|
||||||
|
(record-case exp
|
||||||
|
((ast-sequence src exps)
|
||||||
|
(if (null? exps)
|
||||||
|
(make-void src)
|
||||||
|
(make-sequence src (map-compile exps))))
|
||||||
|
|
||||||
|
((ast-literal src exp)
|
||||||
|
(if (eq? exp *unspecified*)
|
||||||
|
(make-void src)
|
||||||
|
(make-const src exp)))
|
||||||
|
|
||||||
|
((ast-return src exp)
|
||||||
|
(make-application src (make-primitive-ref src 'return)
|
||||||
|
(list (make-application src
|
||||||
|
(make-primitive-ref src 'values)
|
||||||
|
(if (list? exp) (map-compile exp #t) (list (compile exp)))))))
|
||||||
|
|
||||||
|
((ast-function src name arguments argument-gensyms variable-arguments? body)
|
||||||
|
;; ... is always attached because lua functions must ignore
|
||||||
|
;; variable arguments; the parser will catch it if ... is used in a
|
||||||
|
;; function that doesn't have ... in the parameter list
|
||||||
|
(make-lambda src (if name `((name . ,name)) '()) (make-lambda-case src '() arguments '... #f (map (lambda (x) (make-const src #nil)) arguments) (append! argument-gensyms (list '...)) (compile body) #f)))
|
||||||
|
|
||||||
|
((ast-function-call src operator operands)
|
||||||
|
#| (let* ((proc (compile operator))
|
||||||
|
(args (make-application src (make-primitive-ref src 'list) (map-compile operands)))
|
||||||
|
(app-args (make-application src (make-primitive-ref src 'list) (list proc args)))
|
||||||
|
(app (make-application src (make-primitive-ref src 'apply) (list (make-primitive-ref src 'apply) app-args)))) |#
|
||||||
|
(let* ((proc (compile operator))
|
||||||
|
(app (make-application src proc (map-compile operands))))
|
||||||
|
(if (ast-global-ref? operator)
|
||||||
|
(make-sequence src (list
|
||||||
|
(make-application src (make-module-ref src '(language lua runtime) 'check-global-function #t)
|
||||||
|
(list (make-const src (ast-global-ref-name operator))
|
||||||
|
proc))
|
||||||
|
app))
|
||||||
|
app)))
|
||||||
|
|
||||||
|
((ast-local-block src names gensyms initial-values exp)
|
||||||
|
(make-let src names gensyms (map-compile initial-values) (compile exp)))
|
||||||
|
|
||||||
|
((ast-local-ref src name gensym)
|
||||||
|
(make-lexical-ref src name gensym))
|
||||||
|
|
||||||
|
((ast-local-set src name gensym exp)
|
||||||
|
(make-lexical-set src name gensym (compile exp)))
|
||||||
|
|
||||||
|
((ast-global-ref src name)
|
||||||
|
(make-table-ref src (ref-runtime src '*global-env-table*) name))
|
||||||
|
|
||||||
|
((ast-global-set src name exp)
|
||||||
|
(make-table-set! src (ref-runtime src '*global-env-table*) name (compile exp)))
|
||||||
|
|
||||||
|
((ast-table-ref src table key)
|
||||||
|
(make-table-ref src (compile table) (compile key)))
|
||||||
|
|
||||||
|
((ast-table-set src table key exp)
|
||||||
|
(make-table-set! src (compile table) (compile key) (compile exp)))
|
||||||
|
|
||||||
|
((ast-condition src test then else)
|
||||||
|
(make-conditional src (compile test) (compile then) (compile else)))
|
||||||
|
|
||||||
|
((ast-while-loop src condition body)
|
||||||
|
(parameterize
|
||||||
|
((context 'while-loop))
|
||||||
|
(while-loop->tree-il src (compile condition) (compile body))))
|
||||||
|
|
||||||
|
;; TODO: in order for this to have the same semantics as lua, all
|
||||||
|
;; potential subforms of while should introduce their own context,
|
||||||
|
;; so you can't use break inside of a function inside a while loop
|
||||||
|
;; for instance
|
||||||
|
((ast-break src)
|
||||||
|
(unless (or-eqv? (context) 'while-loop 'list-for-loop 'numeric-for-loop)
|
||||||
|
(syntax-error src "no loop to break"))
|
||||||
|
(make-application src (make-module-ref src '(guile) 'throw #t) (list (make-const src 'lua-break)))
|
||||||
|
)
|
||||||
|
|
||||||
|
((ast-list-for-loop src names gs-names exps body)
|
||||||
|
(let* ((gs-iterator (gensym "iterator"))
|
||||||
|
(gs-state (gensym "state"))
|
||||||
|
(gs-variable (gensym "variable"))
|
||||||
|
(gs-iterator2 (gensym "iterator"))
|
||||||
|
(gs-state2 (gensym "state"))
|
||||||
|
(gs-variable2 (gensym "variable"))
|
||||||
|
(gs-loop (gensym "loop")))
|
||||||
|
(parse-tree-il
|
||||||
|
`(letrec*
|
||||||
|
;; names
|
||||||
|
(iterator state variable loop)
|
||||||
|
;; gensyms
|
||||||
|
(,gs-iterator ,gs-state ,gs-variable ,gs-loop)
|
||||||
|
;; vals
|
||||||
|
((void) (void) (void)
|
||||||
|
(lambda ()
|
||||||
|
(lambda-case
|
||||||
|
(,no-arguments
|
||||||
|
(begin
|
||||||
|
;; even more complicated, assigning the values to the loop variables
|
||||||
|
(apply (primitive call-with-values)
|
||||||
|
(lambda () (lambda-case (,no-arguments (apply (lexical iterator ,gs-iterator) (lexical state ,gs-state) (lexical variable ,gs-variable)))))
|
||||||
|
(lambda () (lambda-case ((,names #f #f #f () ,gs-names)
|
||||||
|
;; almost to the actual loop body, hang in there
|
||||||
|
(begin
|
||||||
|
(set! (lexical variable ,gs-variable) (lexical ,(car names) ,(car gs-names)))
|
||||||
|
(if (apply (primitive eq?) (lexical variable ,gs-variable) (const #nil))
|
||||||
|
(apply (@ (guile) throw) (const lua-break))
|
||||||
|
(void))
|
||||||
|
,(parameterize ((context 'list-for-loop)) (unparse-tree-il (compile body)))
|
||||||
|
(apply (lexical loop ,gs-loop))))))))))))
|
||||||
|
;; initialize variables and start loop
|
||||||
|
(begin
|
||||||
|
(apply (primitive call-with-values)
|
||||||
|
(lambda () (lambda-case (,no-arguments ,(unparse-tree-il (make-sequence src (map-compile exps))))))
|
||||||
|
(lambda () (lambda-case (((iterator state variable) #f #f #f () (,gs-iterator2 ,gs-state2 ,gs-variable2))
|
||||||
|
(begin
|
||||||
|
(set! (lexical iterator ,gs-iterator) (lexical iterator ,gs-iterator2))
|
||||||
|
(set! (lexical state ,gs-state) (lexical state ,gs-state2))
|
||||||
|
(set! (lexical variable ,gs-variable) (lexical variable ,gs-variable2)))))))
|
||||||
|
(apply (@ (guile) catch)
|
||||||
|
(const lua-break)
|
||||||
|
(lambda () (lambda-case (,no-arguments
|
||||||
|
(apply (lexical loop ,gs-loop)))))
|
||||||
|
(lambda () (lambda-case (((key) #f #f #f () (,(gensym "key"))) (void))))))))))
|
||||||
|
|
||||||
|
;; TODO: in order for this to have the same semantics as lua, all
|
||||||
|
;; potential subforms of while should introduce their own context,
|
||||||
|
;; so you can't use break inside of a function inside a while loop
|
||||||
|
;; for instance
|
||||||
|
|
||||||
|
((ast-numeric-for-loop src named initial limit step body)
|
||||||
|
;; as per 5.1 manual 2.4.5, the numeric for loop can be decomposed into simpler forms
|
||||||
|
;; still doesn't have proper behavior, should be able to return and break inside a loop
|
||||||
|
(let* ((gs-named (gensym (symbol->string named)))
|
||||||
|
(gs-variable (gensym "variable"))
|
||||||
|
(gs-limit (gensym "limit"))
|
||||||
|
(gs-step (gensym "step"))
|
||||||
|
(gs-loop (gensym "loop"))
|
||||||
|
(while-condition
|
||||||
|
`(if (apply (primitive >) (lexical step ,gs-step) (const 0))
|
||||||
|
(if (apply (primitive <=) (lexical variable ,gs-variable) (lexical limit ,gs-limit))
|
||||||
|
(apply (lexical loop ,gs-loop))
|
||||||
|
(void))
|
||||||
|
(void))))
|
||||||
|
(parse-tree-il
|
||||||
|
`(letrec*
|
||||||
|
;; names
|
||||||
|
(,named variable limit step loop)
|
||||||
|
;; gensyms
|
||||||
|
(,gs-named ,gs-variable ,gs-limit ,gs-step ,gs-loop)
|
||||||
|
;; vals
|
||||||
|
,(cons
|
||||||
|
'(const #f)
|
||||||
|
(append
|
||||||
|
(map (lambda (x) `(apply (@ (language lua runtime) tonumber) ,(unparse-tree-il (compile x)))) (list initial limit step))
|
||||||
|
;; loop body
|
||||||
|
(list
|
||||||
|
`(lambda ()
|
||||||
|
(lambda-case
|
||||||
|
;; no arguments
|
||||||
|
((() #f #f #f () ())
|
||||||
|
;; body
|
||||||
|
(begin
|
||||||
|
(set! (lexical ,named ,gs-named) (lexical variable ,gs-variable))
|
||||||
|
,(parameterize ((context 'numeric-for-loop)) (unparse-tree-il (compile body)))
|
||||||
|
(set! (lexical variable ,gs-variable) (apply (primitive +) (lexical variable ,gs-variable) (lexical step ,gs-step)))
|
||||||
|
,while-condition
|
||||||
|
)))))))
|
||||||
|
;; body
|
||||||
|
(begin
|
||||||
|
;; if not (var and limit and step) then error() end
|
||||||
|
(if (apply (primitive not)
|
||||||
|
(if (apply (@ (language lua runtime) true?) (lexical variable ,gs-variable))
|
||||||
|
(if (apply (@ (language lua runtime) true?) (lexical limit ,gs-limit))
|
||||||
|
(if (apply (@ (language lua runtime) true?) (lexical step ,gs-step))
|
||||||
|
(const #t)
|
||||||
|
(const #f))
|
||||||
|
(const #f))
|
||||||
|
(const #f)))
|
||||||
|
(apply (@ (guile) error))
|
||||||
|
(void))
|
||||||
|
,while-condition
|
||||||
|
)))))
|
||||||
|
|
||||||
|
((ast-table-literal src fields)
|
||||||
|
(let* ((table (make-runtime-application src 'make-table '())))
|
||||||
|
(if (not (null? fields))
|
||||||
|
;; if the table's fields are initialized inside of the literal, we need
|
||||||
|
;; to store it in a variable and initialize its values
|
||||||
|
(let* ((temp-name (gensym " table"))
|
||||||
|
(names (list temp-name))
|
||||||
|
(ref (make-lexical-ref src temp-name temp-name)))
|
||||||
|
(make-let
|
||||||
|
src
|
||||||
|
names names
|
||||||
|
(list table)
|
||||||
|
(make-sequence
|
||||||
|
src
|
||||||
|
(append!
|
||||||
|
(map
|
||||||
|
(lambda (x)
|
||||||
|
(let* ((key (compile (car x)))
|
||||||
|
(value (compile (cdr x))))
|
||||||
|
(make-runtime-application src 'new-index! (list (make-lexical-ref src temp-name temp-name) key value))))
|
||||||
|
fields)
|
||||||
|
(list ref)))))
|
||||||
|
;; otherwise we can just return the fresh table
|
||||||
|
table)))
|
||||||
|
|
||||||
|
((ast-unary-operation src operator right)
|
||||||
|
;; reduce simple negative numbers, like -5, to literals
|
||||||
|
(if (and (eq? operator #\-) (ast-literal? right) (number? (ast-literal-exp right)))
|
||||||
|
(make-const src (- (ast-literal-exp right)))
|
||||||
|
(make-application
|
||||||
|
src
|
||||||
|
(case operator
|
||||||
|
((#\-) (ref-runtime src 'unm))
|
||||||
|
((#\#) (ref-runtime src 'len))
|
||||||
|
((not) (make-primitive-ref src 'not)))
|
||||||
|
(list (compile right)))))
|
||||||
|
|
||||||
|
((ast-binary-operation src operator left right)
|
||||||
|
(let ((left (compile left))
|
||||||
|
(right (compile right)))
|
||||||
|
(case operator
|
||||||
|
((#\+ #\- #\* #\/ #\^ #\< #\> #:<= #:>= #:== #:~= #:concat)
|
||||||
|
(let* ((result
|
||||||
|
(make-runtime-application
|
||||||
|
src
|
||||||
|
(case operator
|
||||||
|
((#\+) 'add)
|
||||||
|
((#\-) 'sub)
|
||||||
|
((#\*) 'mul)
|
||||||
|
((#\/) 'div)
|
||||||
|
((#\^) 'pow)
|
||||||
|
((#\<) 'lt)
|
||||||
|
((#\>) 'lt)
|
||||||
|
((#:<=) 'le)
|
||||||
|
((#:>=) 'le)
|
||||||
|
((#:==) 'eq)
|
||||||
|
((#:~=) 'neq)
|
||||||
|
((#:concat) 'concat)
|
||||||
|
(else (error #:COMPILE "unhandled binary operator" operator)))
|
||||||
|
;; reverse order of arguments for >, >= so they can be implemented on top of <, <=
|
||||||
|
(if (or (eq? operator #\>) (eq? operator #:>=))
|
||||||
|
(list right left)
|
||||||
|
(list left right)))))
|
||||||
|
result))
|
||||||
|
((#:or)
|
||||||
|
(make-lua-conditional
|
||||||
|
src
|
||||||
|
left
|
||||||
|
left
|
||||||
|
right))
|
||||||
|
((#:and)
|
||||||
|
(make-lua-conditional
|
||||||
|
src
|
||||||
|
left
|
||||||
|
right
|
||||||
|
left))
|
||||||
|
(else (error #:COMPILE "unknown binary operator" operator)))))
|
||||||
|
))
|
||||||
|
|
||||||
|
;; exported compiler function
|
||||||
|
(define (compile-tree-il exp env opts)
|
||||||
|
(parameterize
|
||||||
|
((context #f))
|
||||||
|
(values (compile exp) env env)))
|
335
module/language/lua/lexer.scm
Normal file
335
module/language/lua/lexer.scm
Normal file
|
@ -0,0 +1,335 @@
|
||||||
|
;;; Guile Lua --- tokenizer
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;; This is a simple lexer. It generally matches up Lua data types with
|
||||||
|
;; Scheme. Reserved words in Lua, like 'not', are returned as keywords,
|
||||||
|
;; like '#:not'. Operators are returned as keywords like #:==, or
|
||||||
|
;; characters like #\+ when they're only a character long. Identifiers
|
||||||
|
;; are returned as symbols
|
||||||
|
(define-module (language lua lexer)
|
||||||
|
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (srfi srfi-14)
|
||||||
|
#:use-module (srfi srfi-39)
|
||||||
|
|
||||||
|
#:use-module (language lua common)
|
||||||
|
|
||||||
|
#:export (make-lexer)
|
||||||
|
#:export-syntax (define-lua-lexer initialize-lua-lexer!))
|
||||||
|
|
||||||
|
(define stdout (current-output-port))
|
||||||
|
|
||||||
|
(define (source-info port)
|
||||||
|
`((backtrace . #f)
|
||||||
|
(filename . ,(port-filename port))
|
||||||
|
(line . ,(port-line port))
|
||||||
|
(column . ,(port-column port))))
|
||||||
|
|
||||||
|
;; Character predicates
|
||||||
|
|
||||||
|
;; Lua only accepts ASCII characters as of 5.2, so we define our own
|
||||||
|
;; charsets here
|
||||||
|
(define (char-predicate string)
|
||||||
|
(define char-set (string->char-set string))
|
||||||
|
(lambda (c)
|
||||||
|
(and (not (eof-object? c)) (char-set-contains? char-set c))))
|
||||||
|
|
||||||
|
(define is-digit? (char-predicate "0123456789"))
|
||||||
|
(define is-name-first? (char-predicate "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"))
|
||||||
|
(define (is-name? c) (or (is-name-first? c) (is-digit? c)))
|
||||||
|
(define (is-newline? c) (and (char? c) (or (char=? c #\newline) (char=? c #\cr))))
|
||||||
|
|
||||||
|
(define (possible-keyword token)
|
||||||
|
"Convert a symbol to a keyword if it is a reserved word in Lua"
|
||||||
|
(if (or-eqv? token '(return function end if then elseif else true false nil or and do while repeat until local for break in not))
|
||||||
|
(symbol->keyword token)
|
||||||
|
token))
|
||||||
|
|
||||||
|
(define (make-lexer port)
|
||||||
|
;; Buffer management
|
||||||
|
(define buffer (open-output-string))
|
||||||
|
|
||||||
|
(define (drop-buffer)
|
||||||
|
"Clear the buffer and drop the contents"
|
||||||
|
(truncate-file buffer 0))
|
||||||
|
|
||||||
|
(define (clear-buffer)
|
||||||
|
"Clear the buffer and return a string of the contents"
|
||||||
|
(define string (get-output-string buffer))
|
||||||
|
(drop-buffer)
|
||||||
|
string)
|
||||||
|
|
||||||
|
;; Source code information
|
||||||
|
(define saved-source-info #f)
|
||||||
|
|
||||||
|
(define (save-source-info)
|
||||||
|
"Save source code information for a particular location e.g. the beginning
|
||||||
|
of an identifier"
|
||||||
|
(set! saved-source-info (source-info port)))
|
||||||
|
|
||||||
|
(define (get-source-info)
|
||||||
|
"Get source code information"
|
||||||
|
(if saved-source-info
|
||||||
|
saved-source-info
|
||||||
|
(source-info port)))
|
||||||
|
|
||||||
|
(define (save-and-next!)
|
||||||
|
"Shorthand for (write-char (read-char))"
|
||||||
|
(write-char (read-char)))
|
||||||
|
|
||||||
|
(define (eat-comment)
|
||||||
|
"Consume a comment"
|
||||||
|
(let consume ((c (read-char)))
|
||||||
|
(cond ((eof-object? c) #f)
|
||||||
|
((eq? c #\newline) #f)
|
||||||
|
(else (consume (read-char))))))
|
||||||
|
|
||||||
|
(define (get-long-string-nesting-level)
|
||||||
|
"Return the nesting level of a bracketed string, or -1 if it is not one"
|
||||||
|
(define delimiter (read-char))
|
||||||
|
(let* ((count
|
||||||
|
(let loop ((count 0))
|
||||||
|
(if (eq? (peek-char) #\=)
|
||||||
|
(begin
|
||||||
|
(read-char)
|
||||||
|
(loop (+ count 1)))
|
||||||
|
count))))
|
||||||
|
(if (eq? (peek-char) delimiter) count -1)))
|
||||||
|
|
||||||
|
(define (read-long-string string? nest)
|
||||||
|
"Read a long string or comment"
|
||||||
|
;; Skip second bracket
|
||||||
|
(read-char)
|
||||||
|
;; Discard initial newlines, which is what Lua does
|
||||||
|
(while (is-newline? (peek-char))
|
||||||
|
(read-char))
|
||||||
|
;; Read string contents
|
||||||
|
(let loop ((c (peek-char)))
|
||||||
|
(cond
|
||||||
|
;; Error out if end-of-file is encountered
|
||||||
|
((eof-object? c)
|
||||||
|
(syntax-error (get-source-info) (string-append "unfinished long " (if string? "string" "comment"))))
|
||||||
|
;; Check to see if we've reached the end
|
||||||
|
((char=? c #\])
|
||||||
|
(let* ((nest2 (get-long-string-nesting-level)))
|
||||||
|
(if (= nest nest2)
|
||||||
|
(begin
|
||||||
|
(read-char) ;; drop ]
|
||||||
|
(if string?
|
||||||
|
(clear-buffer)
|
||||||
|
(drop-buffer)))
|
||||||
|
;; Compensate for eating up the nesting levels
|
||||||
|
(begin
|
||||||
|
(save-and-next!)
|
||||||
|
(let lp ((n nest2))
|
||||||
|
(if (= n 0)
|
||||||
|
#f
|
||||||
|
(begin
|
||||||
|
(write-char #\=)
|
||||||
|
(lp (- n 1)))))
|
||||||
|
(write-char #\])
|
||||||
|
(loop (peek-char))))))
|
||||||
|
;; Save character and continue
|
||||||
|
(else (save-and-next!)
|
||||||
|
(loop (peek-char))))))
|
||||||
|
|
||||||
|
;; read a single or double quoted string, with escapes
|
||||||
|
(define (read-string delimiter)
|
||||||
|
(read-char) ;; consume delimiter
|
||||||
|
(let loop ((c (peek-char)))
|
||||||
|
(cond
|
||||||
|
;; string ends early
|
||||||
|
((or (eof-object? c) (eq? c #\cr) (eq? c #\newline))
|
||||||
|
(syntax-error (get-source-info) "unfinished string ~S" c))
|
||||||
|
;; string escape
|
||||||
|
((char=? c #\\)
|
||||||
|
;; discard \ and read next character
|
||||||
|
(let* ((escape (begin (read-char) (read-char))))
|
||||||
|
(write-char
|
||||||
|
(case escape
|
||||||
|
((#\a) #\alarm)
|
||||||
|
((#\b) #\backspace)
|
||||||
|
((#\f) #\page)
|
||||||
|
((#\n) #\newline)
|
||||||
|
((#\r) #\return)
|
||||||
|
((#\t) #\tab)
|
||||||
|
((#\v) #\vtab)
|
||||||
|
((#\x) (syntax-error (get-source-info) "hex escapes unsupported"))
|
||||||
|
((#\d) (syntax-error (get-source-info) "decimal escapes unsupported"))
|
||||||
|
(else escape)))
|
||||||
|
(loop (peek-char))))
|
||||||
|
(else
|
||||||
|
(if (eq? c delimiter)
|
||||||
|
(read-char) ;; terminate loop and discard delimiter
|
||||||
|
(begin
|
||||||
|
(save-and-next!)
|
||||||
|
(loop (peek-char)))))))
|
||||||
|
(clear-buffer))
|
||||||
|
|
||||||
|
(define (read-number string)
|
||||||
|
(save-source-info)
|
||||||
|
(let* ((main (string-append (or string "") (begin
|
||||||
|
(while (or (is-digit? (peek-char)) (eq? (peek-char) #\.))
|
||||||
|
(save-and-next!))
|
||||||
|
(clear-buffer))))
|
||||||
|
(exponent
|
||||||
|
(if (or (eq? (peek-char) #\e) (eq? (peek-char) #\E))
|
||||||
|
(begin
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\+)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\-)
|
||||||
|
(save-and-next!)))
|
||||||
|
(if (not (is-digit? (peek-char)))
|
||||||
|
(syntax-error (get-source-info) "expecting number after exponent sign"))
|
||||||
|
(while (is-digit? (peek-char))
|
||||||
|
(save-and-next!))
|
||||||
|
(clear-buffer))
|
||||||
|
#f))
|
||||||
|
(final (string->number main)))
|
||||||
|
(if exponent
|
||||||
|
(* final (expt 10 (string->number exponent)))
|
||||||
|
final)))
|
||||||
|
|
||||||
|
(define (lex)
|
||||||
|
(parameterize ((current-input-port port)
|
||||||
|
(current-output-port buffer))
|
||||||
|
(set! saved-source-info #f)
|
||||||
|
(let loop ()
|
||||||
|
(define c (peek-char))
|
||||||
|
(case c
|
||||||
|
;; Skip spaces
|
||||||
|
((#\newline #\return #\space #\page #\tab #\vtab) (read-char) (loop))
|
||||||
|
|
||||||
|
;; Either a minus (-), or a long comment, which is a - followed by a bracketed string
|
||||||
|
((#\-)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\-)
|
||||||
|
;; It's a comment
|
||||||
|
(begin
|
||||||
|
(read-char)
|
||||||
|
;; Long comment
|
||||||
|
(if (eq? (peek-char) #\[)
|
||||||
|
(let* ((nest (get-long-string-nesting-level)))
|
||||||
|
(drop-buffer)
|
||||||
|
(if (not (negative? nest))
|
||||||
|
(begin
|
||||||
|
(read-long-string #f nest)
|
||||||
|
(drop-buffer)
|
||||||
|
(loop))
|
||||||
|
;; If it's not actually a long comment, drop it
|
||||||
|
(begin (drop-buffer) (eat-comment) (loop))))
|
||||||
|
(begin (eat-comment) (loop))))
|
||||||
|
;; It's a regular minus
|
||||||
|
#\-))
|
||||||
|
|
||||||
|
;; ~=
|
||||||
|
((#\~)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\=)
|
||||||
|
(begin (read-char) #:~=)
|
||||||
|
(syntax-error (get-source-info) "expected = after ~ but got ~c" c)))
|
||||||
|
|
||||||
|
;; < or <=
|
||||||
|
((#\<)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\=) (begin (read-char) #:<=) #\<))
|
||||||
|
|
||||||
|
;; > or >=
|
||||||
|
((#\>)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\=) (begin (read-char) #:>=) #\>))
|
||||||
|
|
||||||
|
;; = or ==
|
||||||
|
((#\=)
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\=)
|
||||||
|
(begin (read-char) #:==)
|
||||||
|
#:=))
|
||||||
|
|
||||||
|
;; . can mean one of: floating point number (.12345), table field access (plain .),
|
||||||
|
;; concatenation operator (..) or the variable argument indicator (...)
|
||||||
|
((#\.)
|
||||||
|
(read-char)
|
||||||
|
(if (is-digit? (peek-char))
|
||||||
|
(read-number ".")
|
||||||
|
(if (eq? (peek-char) #\.)
|
||||||
|
(begin
|
||||||
|
(read-char)
|
||||||
|
(if (eq? (peek-char) #\.)
|
||||||
|
(begin (read-char) #:dots)
|
||||||
|
#:concat))
|
||||||
|
#\.)))
|
||||||
|
|
||||||
|
;; Double-quoted string
|
||||||
|
((#\") (read-string #\"))
|
||||||
|
|
||||||
|
;; Single-quoted string
|
||||||
|
((#\') (read-string #\'))
|
||||||
|
|
||||||
|
;; Bracketed string
|
||||||
|
((#\[)
|
||||||
|
(save-source-info)
|
||||||
|
(let* ((nest (get-long-string-nesting-level)))
|
||||||
|
(if (eq? nest -1)
|
||||||
|
#\[
|
||||||
|
(read-long-string #t nest))))
|
||||||
|
|
||||||
|
;; Characters that are allowed to fall through directly to the parser
|
||||||
|
((#\; #\( #\) #\,
|
||||||
|
#\+ #\/ #\*
|
||||||
|
#\^ #\{ #\} #\] #\: #\#) (read-char))
|
||||||
|
|
||||||
|
;; Numbers
|
||||||
|
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9)
|
||||||
|
(save-source-info)
|
||||||
|
(save-and-next!)
|
||||||
|
(read-number #f))
|
||||||
|
|
||||||
|
(else
|
||||||
|
(cond ((eof-object? c) c)
|
||||||
|
;; Identifier or keyword
|
||||||
|
((is-name-first? c)
|
||||||
|
(save-and-next!)
|
||||||
|
(save-source-info)
|
||||||
|
(while (is-name? (peek-char))
|
||||||
|
(save-and-next!))
|
||||||
|
(possible-keyword (string->symbol (clear-buffer))))
|
||||||
|
(else (syntax-error (get-source-info) "disallowed character ~c" c))))
|
||||||
|
) ; case
|
||||||
|
) ; loop
|
||||||
|
) ; parameterize
|
||||||
|
) ; lex
|
||||||
|
(values get-source-info lex)) ; make-lexer
|
||||||
|
|
||||||
|
(define-syntax define-lua-lexer
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ a b)
|
||||||
|
(begin
|
||||||
|
(define a #f)
|
||||||
|
(define b #f)))))
|
||||||
|
|
||||||
|
(define-syntax initialize-lua-lexer!
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ port a b)
|
||||||
|
(receive (get-source-info lex)
|
||||||
|
(make-lexer port)
|
||||||
|
(set! a get-source-info)
|
||||||
|
(set! b lex)))))
|
892
module/language/lua/parser.scm
Normal file
892
module/language/lua/parser.scm
Normal file
|
@ -0,0 +1,892 @@
|
||||||
|
;;; Guile Lua --- parser
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
;; parser.scm --- lua parser
|
||||||
|
|
||||||
|
;; This parser is based heavily on Lua's parser. It does not use
|
||||||
|
;; lalr-scm, because Lua's grammar is a little too plucky. Unlike Lua's
|
||||||
|
;; parser, it returns an abstract syntax tree instead of incrementally
|
||||||
|
;; compiling the source.
|
||||||
|
|
||||||
|
(define-module (language lua parser)
|
||||||
|
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module (rnrs control)
|
||||||
|
|
||||||
|
#:use-module (language lua common)
|
||||||
|
#:use-module (language lua lexer)
|
||||||
|
#:use-module (language lua runtime)
|
||||||
|
|
||||||
|
#:export (make-parser read-lua))
|
||||||
|
|
||||||
|
;; Implicitly named records
|
||||||
|
(letrec-syntax
|
||||||
|
((define-record
|
||||||
|
(lambda (stx)
|
||||||
|
(define (id template-id . rest)
|
||||||
|
(datum->syntax template-id (string->symbol (apply string-append (map (lambda (x) (if (string? x) x (symbol->string (syntax->datum x)))) rest)))))
|
||||||
|
(syntax-case stx ()
|
||||||
|
((_ name field ...)
|
||||||
|
(with-syntax
|
||||||
|
((constructor (id #'name "make-" #'name))
|
||||||
|
(predicate (id #'name #'name "?"))
|
||||||
|
((setter ...)
|
||||||
|
(map (lambda (x) (id #'name #'name "-" x "!")) #'(field ...)))
|
||||||
|
((getter ...)
|
||||||
|
(map (lambda (x) (id #'name #'name "-" x)) #'(field ...))))
|
||||||
|
|
||||||
|
#'(begin
|
||||||
|
(define-record-type name
|
||||||
|
(constructor field ...)
|
||||||
|
predicate
|
||||||
|
(field getter setter) ...)
|
||||||
|
(export name)
|
||||||
|
(export constructor)
|
||||||
|
(export predicate)
|
||||||
|
(export getter)
|
||||||
|
...
|
||||||
|
(export setter)
|
||||||
|
...
|
||||||
|
))))))
|
||||||
|
|
||||||
|
(define-ast
|
||||||
|
(lambda (stx)
|
||||||
|
(define (id template-id . rest)
|
||||||
|
(datum->syntax template-id (string->symbol (apply string-append (map (lambda (x) (if (string? x) x (symbol->string (syntax->datum x)))) rest)))))
|
||||||
|
|
||||||
|
(syntax-case stx ()
|
||||||
|
((_ "aux" name field ...)
|
||||||
|
#`(define-record #,(id #'name "ast-" #'name) #,(id #'name "src") field ...))
|
||||||
|
|
||||||
|
((_ (name field ...) ...)
|
||||||
|
#`(begin
|
||||||
|
(define-ast "aux" name field ...)
|
||||||
|
...))))))
|
||||||
|
|
||||||
|
;; Environments & bindings
|
||||||
|
(define-record environment parent bindings)
|
||||||
|
(define-record binding name gensym type)
|
||||||
|
|
||||||
|
;; Abstract syntax tree -- all of these records are automatically
|
||||||
|
;; prefixed with 'ast- and have an SRC field attached.
|
||||||
|
(define-ast
|
||||||
|
(unary-not exp)
|
||||||
|
(literal exp)
|
||||||
|
(sequence exps)
|
||||||
|
(return exp)
|
||||||
|
(condition test then else)
|
||||||
|
(local-block names gensyms initial-values exp)
|
||||||
|
(unary-operation operator right)
|
||||||
|
(local-ref name gensym)
|
||||||
|
(local-set name gensym exp)
|
||||||
|
(global-ref name)
|
||||||
|
(global-set name exp)
|
||||||
|
(table-ref table key)
|
||||||
|
(table-set table key exp)
|
||||||
|
(table-literal fields)
|
||||||
|
(while-loop condition body)
|
||||||
|
(numeric-for-loop named initial limit step body)
|
||||||
|
(list-for-loop names gs-names exps body)
|
||||||
|
(break)
|
||||||
|
(function name arguments argument-gensyms variable-arguments? body)
|
||||||
|
(function-call operator operands)
|
||||||
|
(binary-operation operator left right)
|
||||||
|
(variable-arguments))
|
||||||
|
|
||||||
|
) ; letrec-syntax
|
||||||
|
|
||||||
|
;; Constants
|
||||||
|
(define *nil-literal* (make-ast-literal #f #nil))
|
||||||
|
(define *void-literal* (make-ast-literal #f *unspecified*))
|
||||||
|
(define *default-for-step* (make-ast-literal #f 1))
|
||||||
|
|
||||||
|
(define (end-of-chunk? token)
|
||||||
|
"Returns true if TOKEN denotes the end of a grammatical chunk."
|
||||||
|
(or (or-eqv? token #:else #:elseif #:end #:until) (eof-object? token)))
|
||||||
|
|
||||||
|
(define (token/type t)
|
||||||
|
(cond ((number? t) 'NUMBER)
|
||||||
|
((eof-object? t) 'EOS)
|
||||||
|
((symbol? t) 'NAME)
|
||||||
|
((string? t) 'STRING)
|
||||||
|
(else
|
||||||
|
(case t
|
||||||
|
((#\. #\( #\) #\, #\- #\< #\; #\+ #\{ #\} #\[ #\] #\: #\#
|
||||||
|
#:function #:end #:if #:return #:elseif #:then #:else #:true #:false
|
||||||
|
#:nil #:== #:~= #:= #\> #:>= #:<= #:local #:dots #:break #:do #:in) t)
|
||||||
|
(else (error 'TOKEN/TYPE t))))))
|
||||||
|
|
||||||
|
;; infix operator parsing
|
||||||
|
(define (binary-operator? t)
|
||||||
|
"Return #t if the token may be a binary operator"
|
||||||
|
(or-eqv? t #\+ #\* #\/ #\- #\^ #\< #\> #:== #:~= #:and #:or #:concat))
|
||||||
|
|
||||||
|
(define (unary-operator? t)
|
||||||
|
"Return #t if the token may be a unary operator"
|
||||||
|
(or-eqv? t #\- #\# #:not))
|
||||||
|
|
||||||
|
;; Operator precedence
|
||||||
|
(define *unary-priority* 80)
|
||||||
|
|
||||||
|
(define (priority o)
|
||||||
|
"Return the priority of a given operator token"
|
||||||
|
(case o
|
||||||
|
((#:or) 10)
|
||||||
|
((#:and) 20)
|
||||||
|
((#:== #:~= #:<= #:>= #\< #\>) 30)
|
||||||
|
((#\+ #\-) 60)
|
||||||
|
((#\* #\/ #\%) 70)
|
||||||
|
((#\^ #:concat) 99)))
|
||||||
|
|
||||||
|
;;;;; TREE-IL UTILITIES
|
||||||
|
|
||||||
|
(define (make-lua-assignment left right)
|
||||||
|
"Generate an assignment from a variable and an expression"
|
||||||
|
(cond ((ast-global-ref? left)
|
||||||
|
(make-ast-global-set (ast-global-ref-src left) (ast-global-ref-name left) right))
|
||||||
|
((ast-local-ref? left)
|
||||||
|
(make-ast-local-set (ast-local-ref-src left) (ast-local-ref-name left) (ast-local-ref-gensym left) right))
|
||||||
|
((ast-table-ref? left)
|
||||||
|
(make-ast-table-set (ast-table-ref-src left) (ast-table-ref-table left) (ast-table-ref-key left) right))
|
||||||
|
(else
|
||||||
|
(error 'MAKE-LUA-ASSIGNMENT "should not happen"))))
|
||||||
|
|
||||||
|
(define (wrap-expression-in-environment src e x)
|
||||||
|
"Wrap an expression in an enclosing lexical environment if necessary"
|
||||||
|
(let* ((bindings (map cdr (environment-bindings e)))
|
||||||
|
(locals (filter-map (lambda (b) (if (eq? (binding-type b) 'local) b #f)) bindings)))
|
||||||
|
(if (null? locals)
|
||||||
|
x
|
||||||
|
(make-ast-local-block src (map binding-name locals) (map binding-gensym locals) (map (lambda (c) *nil-literal*) locals) x))))
|
||||||
|
|
||||||
|
;;;;; PARSER
|
||||||
|
|
||||||
|
(define (make-parser port)
|
||||||
|
;; Variables that will be set to the results of MAKE-LEXER.
|
||||||
|
(define-lua-lexer get-source-info lexer)
|
||||||
|
|
||||||
|
;; We need two tokens of lookahead
|
||||||
|
(define token2 #f)
|
||||||
|
|
||||||
|
(define (lookahead!)
|
||||||
|
(set! token2 (lexer)))
|
||||||
|
|
||||||
|
;; Current token
|
||||||
|
(define token)
|
||||||
|
|
||||||
|
;; Lexical environment
|
||||||
|
(define environment #f)
|
||||||
|
|
||||||
|
;; True if inside a function and the function accepts variable arguments
|
||||||
|
(define *vararg-function* #f)
|
||||||
|
|
||||||
|
;;;;; ENVIRONMENTS
|
||||||
|
(define (enter-environment!)
|
||||||
|
"Create a new environment, and set ENVIRONMENT to it"
|
||||||
|
(set! environment
|
||||||
|
(make-environment environment '())))
|
||||||
|
|
||||||
|
(define (leave-environment!)
|
||||||
|
"Set ENVIRONMENT to the current ENVIRONMENT's parent"
|
||||||
|
(if (not environment)
|
||||||
|
(error 'LEAVE-ENVIRONMENT! "should not happen"))
|
||||||
|
(set! environment
|
||||||
|
(environment-parent environment)))
|
||||||
|
|
||||||
|
;; Type may be 'parameter or 'local
|
||||||
|
(define (environment-define! name type)
|
||||||
|
"Define a new variable with NAME and TYPE"
|
||||||
|
(if (not (member name (environment-bindings environment)))
|
||||||
|
(environment-bindings! environment (alist-cons name
|
||||||
|
(make-binding
|
||||||
|
name
|
||||||
|
(gensym (string-append " " (symbol->string name)))
|
||||||
|
type)
|
||||||
|
(environment-bindings environment)))))
|
||||||
|
|
||||||
|
;; Environment lookup procedures -- these fail silently and return #f,
|
||||||
|
;; because Lua allows global variables to be referenced without being
|
||||||
|
;; predefined
|
||||||
|
|
||||||
|
(define (environment-lookup-aux name . e)
|
||||||
|
"Given a variable's NAME, look up its binding."
|
||||||
|
(set! e (if (null? e) environment (car e )))
|
||||||
|
(if e
|
||||||
|
(let ((binding (assq-ref (environment-bindings e) name)))
|
||||||
|
(if binding
|
||||||
|
binding
|
||||||
|
(environment-lookup-aux name (environment-parent e))))
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define (environment-lookup-gensym name)
|
||||||
|
"Given a variable's NAME, look up its gensym"
|
||||||
|
(define binding (environment-lookup-aux name))
|
||||||
|
(if binding
|
||||||
|
(binding-gensym binding)
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define (environment-lookup-type name)
|
||||||
|
"Given a variable's NAME, look up its global"
|
||||||
|
(define binding (environment-lookup-aux name))
|
||||||
|
(if binding
|
||||||
|
(binding-type binding)
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define (resolve-ref src name)
|
||||||
|
"Determine whether a variable reference is global or local"
|
||||||
|
(let* ((binding (environment-lookup-gensym name)))
|
||||||
|
(if binding
|
||||||
|
(make-ast-local-ref src name binding)
|
||||||
|
(make-ast-global-ref src name))))
|
||||||
|
|
||||||
|
;;;;; LEXER INTERACTION
|
||||||
|
|
||||||
|
(define (advance-aux)
|
||||||
|
"Read a new token and store it in TOKEN"
|
||||||
|
(if token2
|
||||||
|
(begin
|
||||||
|
(set! token token2)
|
||||||
|
(set! token2 #f))
|
||||||
|
(set! token (lexer))))
|
||||||
|
|
||||||
|
(define-syntax advance!
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ x) (begin (advance-aux) x))
|
||||||
|
((_) (advance-aux))))
|
||||||
|
|
||||||
|
(define (assert-token-type type)
|
||||||
|
"Throw an error if the current token does not have the expected type"
|
||||||
|
(if (not (equal? (token/type token) type))
|
||||||
|
(syntax-error (get-source-info) "expected ~a" type)))
|
||||||
|
|
||||||
|
(define (maybe-skip-next! c)
|
||||||
|
"Skip a token"
|
||||||
|
(if (equal? token c)
|
||||||
|
(advance! #t)
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define (enforce-next! expect)
|
||||||
|
"Throw an error if the current token is not the expected token"
|
||||||
|
(unless (maybe-skip-next! expect)
|
||||||
|
(syntax-error (get-source-info) "expected '~A' but got '~A'" expect token)))
|
||||||
|
|
||||||
|
;;;;; GRAMMAR
|
||||||
|
|
||||||
|
;; single-name -> NAME
|
||||||
|
(define (single-name . return-src?)
|
||||||
|
(define save token)
|
||||||
|
(define src (get-source-info))
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(advance!)
|
||||||
|
(if (not (null? return-src?))
|
||||||
|
(values src save)
|
||||||
|
save))
|
||||||
|
|
||||||
|
;; single-variable -> single-name
|
||||||
|
(define (single-variable)
|
||||||
|
(receive (src save)
|
||||||
|
(single-name #:return-src #t)
|
||||||
|
(resolve-ref src save)))
|
||||||
|
|
||||||
|
;; application-arguments -> '(' [ expression-list ] ')' | STRING | TABLE
|
||||||
|
(define (application-arguments)
|
||||||
|
(cond ((eq? (token/type token) 'STRING)
|
||||||
|
(let* ((string token))
|
||||||
|
(advance!)
|
||||||
|
(list (make-ast-literal #f string))))
|
||||||
|
((eq? token #\{)
|
||||||
|
;; TODO: table constructor
|
||||||
|
;; '('
|
||||||
|
(list (table-literal)))
|
||||||
|
((eq? token #\()
|
||||||
|
(advance!)
|
||||||
|
(if (eq? token #\))
|
||||||
|
;; ')'
|
||||||
|
(advance! '())
|
||||||
|
;; [ expression-list ]
|
||||||
|
(let* ((arguments (expression-list)))
|
||||||
|
;; ')'
|
||||||
|
(enforce-next! #\))
|
||||||
|
arguments)))
|
||||||
|
(else (error 'APPLICATION-ARGUMENTS "should not happen"))))
|
||||||
|
|
||||||
|
;; prefix-expression -> NAME | '(' expression ')'
|
||||||
|
(define (prefix-expression)
|
||||||
|
(cond
|
||||||
|
;; NAME
|
||||||
|
((eq? (token/type token) 'NAME) (single-variable))
|
||||||
|
;; '('
|
||||||
|
((eq? token #\()
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
;; expression
|
||||||
|
(let* ((save (expression)))
|
||||||
|
;; ')'
|
||||||
|
(enforce-next! #\))
|
||||||
|
;; finished
|
||||||
|
save)))
|
||||||
|
(else (syntax-error (get-source-info) "unexpected symbol ~a" token))))
|
||||||
|
|
||||||
|
;; index -> '[' expression ']'
|
||||||
|
(define (index)
|
||||||
|
(enforce-next! #\[)
|
||||||
|
(let* ((indice (expression)))
|
||||||
|
(enforce-next! #\])
|
||||||
|
indice))
|
||||||
|
|
||||||
|
;; field-selector -> '.' NAME
|
||||||
|
(define (field-selector src prefix)
|
||||||
|
(make-ast-table-ref src prefix (make-ast-literal src (symbol->string (single-name)))))
|
||||||
|
|
||||||
|
;; primary-expression -> prefix-expression { field-selector [ application-arguments ] | index | application-arguments }
|
||||||
|
(define (primary-expression)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; prefix-expression
|
||||||
|
(define prefix (prefix-expression))
|
||||||
|
(let lp ((expr prefix))
|
||||||
|
(case (token/type token)
|
||||||
|
;; field-selector
|
||||||
|
((#\.) (advance!) (lp (field-selector src expr)))
|
||||||
|
;; index
|
||||||
|
((#\[)
|
||||||
|
(let* ((indice (index)))
|
||||||
|
(lp (make-ast-table-ref src expr indice))))
|
||||||
|
;; ':' NAME application-arguments
|
||||||
|
((#\:)
|
||||||
|
(advance!)
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(let* ((name (single-name)))
|
||||||
|
(lp
|
||||||
|
(make-ast-function-call src
|
||||||
|
(make-ast-table-ref src expr (make-ast-literal src (symbol->string name)))
|
||||||
|
(cons expr (application-arguments))))))
|
||||||
|
;; application-arguments
|
||||||
|
((#\( STRING #\{)
|
||||||
|
(lp (make-ast-function-call src expr (application-arguments))))
|
||||||
|
(else expr))))
|
||||||
|
|
||||||
|
;; expression-statement -> function | assignment
|
||||||
|
(define (expression-statement)
|
||||||
|
(define primary (primary-expression))
|
||||||
|
(if (ast-function-call? primary)
|
||||||
|
primary
|
||||||
|
(assignment primary)))
|
||||||
|
|
||||||
|
|
||||||
|
;; record-field -> (NAME | index) '=' expression
|
||||||
|
(define (record-field)
|
||||||
|
(let* ((indice
|
||||||
|
(cond
|
||||||
|
;; NAME
|
||||||
|
((eq? (token/type token) 'NAME)
|
||||||
|
(let ((tmp (make-ast-literal #f (symbol->string token))))
|
||||||
|
(advance!)
|
||||||
|
tmp))
|
||||||
|
;; index
|
||||||
|
(else (index))))
|
||||||
|
(value
|
||||||
|
(begin
|
||||||
|
;; '='
|
||||||
|
(enforce-next! #:=)
|
||||||
|
;; expression
|
||||||
|
(expression))))
|
||||||
|
(values indice value)))
|
||||||
|
|
||||||
|
;; field -> expression | record-field
|
||||||
|
(define (field)
|
||||||
|
(case (token/type token)
|
||||||
|
((NAME)
|
||||||
|
(lookahead!)
|
||||||
|
(if (eq? token2 #:=)
|
||||||
|
(record-field)
|
||||||
|
(values #f (expression))))
|
||||||
|
((#\[) (record-field))
|
||||||
|
(else (values #f (expression)))))
|
||||||
|
|
||||||
|
;; field-separator -> ',' | ';'
|
||||||
|
;; table-fields -> [ field { field-separator field } [ field-separator ] ]
|
||||||
|
(define (table-fields src)
|
||||||
|
(if (eq? token #\})
|
||||||
|
'()
|
||||||
|
(let loop ((implicit-indice 1)
|
||||||
|
(tree '()))
|
||||||
|
(if (eq? token #\})
|
||||||
|
(reverse! tree)
|
||||||
|
(receive
|
||||||
|
(indice expr)
|
||||||
|
(field)
|
||||||
|
;; field-separator
|
||||||
|
(maybe-skip-next! #\,)
|
||||||
|
(maybe-skip-next! #\;)
|
||||||
|
|
||||||
|
(loop
|
||||||
|
(if (not indice) (+ implicit-indice 1) implicit-indice)
|
||||||
|
(cons
|
||||||
|
(cons (or indice (make-ast-literal src implicit-indice)) expr)
|
||||||
|
tree)))))))
|
||||||
|
|
||||||
|
;; table-literal -> '{' table-fields '}'
|
||||||
|
(define (table-literal)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; '{'
|
||||||
|
(enforce-next! #\{)
|
||||||
|
;; bind the table to a temporary variable with LET as it's needed in order to initialize the table
|
||||||
|
(let* ((result (make-ast-table-literal src (table-fields src))))
|
||||||
|
result
|
||||||
|
(enforce-next! #\})
|
||||||
|
result))
|
||||||
|
|
||||||
|
;; parameter-list -> [ parameter { ',' parameter } ]
|
||||||
|
(define (parameter-list function-name)
|
||||||
|
(if (eq? token #\))
|
||||||
|
(values '() #f)
|
||||||
|
(let lp ((parameters '()))
|
||||||
|
;; parameter
|
||||||
|
(let* ((parameters
|
||||||
|
(if (eq? (token/type token) 'NAME)
|
||||||
|
(append! parameters (list token))
|
||||||
|
(if (eq? token #:dots)
|
||||||
|
(values parameters #f)
|
||||||
|
(syntax-error (get-source-info) "expected either a name or ... in the parameter list of '~a', but got ~a" function-name token))))
|
||||||
|
(last-token token))
|
||||||
|
(advance!)
|
||||||
|
(if (eq? token #\,)
|
||||||
|
(if (eq? last-token #:dots)
|
||||||
|
(syntax-error (get-source-info) "expected ')' after ... in the parameter list of '~a'" function-name)
|
||||||
|
(advance! (lp parameters)))
|
||||||
|
(values parameters (eq? last-token #:dots)))))))
|
||||||
|
|
||||||
|
;; function-body -> '(' parameter-list ')' chunk END
|
||||||
|
(define* (function-body #:optional (src (get-source-info)) (implicit-self? #f) (name 'anonymous))
|
||||||
|
;; '('
|
||||||
|
(enforce-next! #\()
|
||||||
|
;; parameter-list
|
||||||
|
(receive (parameters variable-arguments?)
|
||||||
|
(parameter-list name)
|
||||||
|
(let* ((old-vararg-function *vararg-function*))
|
||||||
|
(set! *vararg-function* variable-arguments?)
|
||||||
|
(enforce-next! #\))
|
||||||
|
;; create function
|
||||||
|
(enter-environment!)
|
||||||
|
(when implicit-self?
|
||||||
|
(environment-define! 'self 'parameter))
|
||||||
|
(for-each (lambda (p) (environment-define! p 'parameter)) parameters)
|
||||||
|
;; chunk
|
||||||
|
(let* ((body (chunk))
|
||||||
|
(parameter-gensyms (map environment-lookup-gensym parameters))
|
||||||
|
(result
|
||||||
|
(make-ast-function src (if (eq? name 'anonymous) #f name)
|
||||||
|
(if implicit-self? (append parameters '(self)) parameters)
|
||||||
|
(if implicit-self? (append parameter-gensyms (list (environment-lookup-gensym 'self))) parameter-gensyms)
|
||||||
|
variable-arguments? (if (null? body) *void-literal* body))))
|
||||||
|
(leave-environment!)
|
||||||
|
;; END
|
||||||
|
(enforce-next! #:end)
|
||||||
|
(set! *vararg-function* old-vararg-function)
|
||||||
|
result))))
|
||||||
|
|
||||||
|
;; expression-list -> expression { ',' expression }
|
||||||
|
(define (expression-list)
|
||||||
|
(let loop ((tree (list (expression))))
|
||||||
|
;; { ',' expression }
|
||||||
|
(if (maybe-skip-next! #\,)
|
||||||
|
(loop (cons (expression) tree))
|
||||||
|
;; finished
|
||||||
|
(reverse! tree))))
|
||||||
|
|
||||||
|
;; simple-expression -> (nil | true | false | NUMBER | STRING) | table-literal | FUNCTION function-body
|
||||||
|
(define (simple-expression)
|
||||||
|
(define src (get-source-info))
|
||||||
|
(receive
|
||||||
|
(advance? result)
|
||||||
|
(case (token/type token)
|
||||||
|
;; (nil | true | false | NUMBER | STRING)
|
||||||
|
((#:true #:false #:nil NUMBER STRING)
|
||||||
|
(values
|
||||||
|
#t
|
||||||
|
(make-ast-literal
|
||||||
|
src
|
||||||
|
(cond ((eq? token #:true) #t)
|
||||||
|
((eq? token #:false) #f)
|
||||||
|
((eq? token #:nil) #nil)
|
||||||
|
(else token)))))
|
||||||
|
;; table-literal
|
||||||
|
((#\{) (values #f (table-literal)))
|
||||||
|
;; ...
|
||||||
|
((#:dots)
|
||||||
|
(unless *vararg-function*
|
||||||
|
(syntax-error src "cannot use '...' outside of a variable arguments function"))
|
||||||
|
(values #t (make-ast-variable-arguments src)))
|
||||||
|
;; FUNCTION function-body
|
||||||
|
((#:function) (advance!) (values #f (function-body src)))
|
||||||
|
;; primary-expression
|
||||||
|
(else (values #f (primary-expression))))
|
||||||
|
(if advance?
|
||||||
|
(advance!))
|
||||||
|
result))
|
||||||
|
|
||||||
|
;; subexpression -> (simple-expression | unary-operator subexpression) { binary-operator subexpression }
|
||||||
|
(define (subexpression limit)
|
||||||
|
(define left)
|
||||||
|
;; test for preceding unary operator
|
||||||
|
(set! left
|
||||||
|
;; (simple-expression | unary-operator subexpression)
|
||||||
|
(if (unary-operator? token)
|
||||||
|
;; unary-operator subexpression
|
||||||
|
(let* ((src (get-source-info))
|
||||||
|
(operator token))
|
||||||
|
(advance!)
|
||||||
|
(make-ast-unary-operation src operator (subexpression *unary-priority*)))
|
||||||
|
;; simple-expression
|
||||||
|
;; note: simple-expression may advance the current token
|
||||||
|
(simple-expression)))
|
||||||
|
|
||||||
|
(let loop ((left left))
|
||||||
|
;; { binary-operator subexpression }
|
||||||
|
(if (and (binary-operator? token) (> (priority token) limit))
|
||||||
|
(let* ((src (get-source-info))
|
||||||
|
(operator token))
|
||||||
|
(advance!)
|
||||||
|
;; read next expression with higher priorities
|
||||||
|
(let* ((right (subexpression (priority operator))))
|
||||||
|
(loop (make-ast-binary-operation src operator left right))))
|
||||||
|
;; finished
|
||||||
|
left)))
|
||||||
|
|
||||||
|
;; expression -> subexpression
|
||||||
|
(define (expression)
|
||||||
|
(subexpression 0))
|
||||||
|
|
||||||
|
;; while-statement -> WHILE expression DO chunk END
|
||||||
|
(define (while-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; WHILE
|
||||||
|
(advance!)
|
||||||
|
;; expression
|
||||||
|
(let* ((condition (expression)))
|
||||||
|
;; DO
|
||||||
|
(enforce-next! #:do)
|
||||||
|
;; chunk
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
;; END
|
||||||
|
(enforce-next! #:end)
|
||||||
|
(make-ast-while-loop src condition body))))
|
||||||
|
|
||||||
|
;; return-statement -> RETURN expression-list
|
||||||
|
(define (return-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
|
||||||
|
;; RETURN
|
||||||
|
(advance!)
|
||||||
|
|
||||||
|
(make-ast-return src (if (or (end-of-chunk? token) (eq? token #\;))
|
||||||
|
*void-literal*
|
||||||
|
(expression-list))))
|
||||||
|
|
||||||
|
(define (parse-assignment src left right)
|
||||||
|
;; and then parses it, branching to handle overflows on either side if necessary
|
||||||
|
(make-ast-sequence
|
||||||
|
src
|
||||||
|
(let loop ((left left)
|
||||||
|
(right right)
|
||||||
|
(tree '()))
|
||||||
|
(cond
|
||||||
|
;; no overflows, and finished
|
||||||
|
((and (null? left) (null? right))
|
||||||
|
(reverse! tree))
|
||||||
|
;; no overflows, not finished
|
||||||
|
((and (not (null? left)) (not (null? right)))
|
||||||
|
(loop (cdr left)
|
||||||
|
(cdr right)
|
||||||
|
(cons (make-lua-assignment (car left) (car right)) tree)))
|
||||||
|
;; overflow on right, evaluate extra expressions on the right
|
||||||
|
((and (null? left) (not (null? right)))
|
||||||
|
(reverse! (append! right tree)))
|
||||||
|
;; overflow on left, set all overflowed expressions to nil
|
||||||
|
((and (not (null? left)) (null? right))
|
||||||
|
(let loop ((tree tree)
|
||||||
|
(rest left))
|
||||||
|
(let* ((il (make-lua-assignment (car rest) *nil-literal*))
|
||||||
|
(rest (cdr rest)))
|
||||||
|
(if (null? rest)
|
||||||
|
(reverse! (cons il tree))
|
||||||
|
(loop (cons il tree) (cdr rest))))))
|
||||||
|
(else (error 'PARSE-ASSIGNMENT "should not happen"))))))
|
||||||
|
|
||||||
|
;; assignment -> '=' expression-list | ',' primary-expression assignment
|
||||||
|
(define (assignment first)
|
||||||
|
;; assignments are unfortunately complicated because multiple variables may
|
||||||
|
;; be assigned to multiple expressions in a single assignment, and the
|
||||||
|
;; number of variables and expressions need not match
|
||||||
|
|
||||||
|
;; so this function accumulates the entire assignment
|
||||||
|
(let* ((src (get-source-info))
|
||||||
|
(left (let loop ((x first)
|
||||||
|
(tree '()))
|
||||||
|
(set! tree (cons x tree))
|
||||||
|
(if (eq? token #\,)
|
||||||
|
(advance! (loop (primary-expression) tree))
|
||||||
|
(reverse! tree))))
|
||||||
|
|
||||||
|
(right (begin
|
||||||
|
(enforce-next! #:=)
|
||||||
|
(expression-list))))
|
||||||
|
(parse-assignment src left right)
|
||||||
|
|
||||||
|
) ; let*
|
||||||
|
) ; assignment
|
||||||
|
|
||||||
|
;; then-chunk -> (IF | ELSEIF) expression THEN chunk
|
||||||
|
(define (then-chunk)
|
||||||
|
;; IF | ELSEIF
|
||||||
|
(advance!)
|
||||||
|
;; expression
|
||||||
|
(let* ((condition (expression)))
|
||||||
|
;; THEN
|
||||||
|
(enforce-next! #:then)
|
||||||
|
;; chunk
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
(values condition body))))
|
||||||
|
|
||||||
|
;; if-statement -> then-chunk { then-chunk } [ELSE chunk] END
|
||||||
|
(define (if-statement)
|
||||||
|
(define if-src (get-source-info))
|
||||||
|
(define x
|
||||||
|
(receive (test then)
|
||||||
|
(then-chunk)
|
||||||
|
(make-ast-condition
|
||||||
|
if-src test then
|
||||||
|
(let lp ()
|
||||||
|
(define src (get-source-info))
|
||||||
|
(if (eq? token #:elseif)
|
||||||
|
(receive (test then)
|
||||||
|
(then-chunk)
|
||||||
|
(make-ast-condition src test then (lp)))
|
||||||
|
(if (eq? token #:else)
|
||||||
|
(advance! (chunk))
|
||||||
|
*void-literal*))))))
|
||||||
|
(enforce-next! #:end)
|
||||||
|
x)
|
||||||
|
|
||||||
|
;; repeat-statement -> REPEAT chunk UNTIL expression
|
||||||
|
(define (repeat-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; REPEAT
|
||||||
|
(advance!)
|
||||||
|
;; chunk
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
;; UNTIL
|
||||||
|
(enforce-next! #:until)
|
||||||
|
;; expression
|
||||||
|
(let* ((condition (expression)))
|
||||||
|
(make-ast-while-loop
|
||||||
|
src
|
||||||
|
(make-ast-unary-operation src 'not condition)
|
||||||
|
body))))
|
||||||
|
|
||||||
|
;; function-statement -> FUNCTION NAME { field-selector } [ ':' NAME ] function-body
|
||||||
|
(define (function-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; FUNCTION NAME
|
||||||
|
(define name (advance! (single-name)))
|
||||||
|
|
||||||
|
(receive (prefix type)
|
||||||
|
(let lp ((last-expr (resolve-ref src name)))
|
||||||
|
(if (eq? token #\.)
|
||||||
|
;; { '.' NAME }
|
||||||
|
(let* ((name (advance! (single-name))))
|
||||||
|
(if (eq? token #\()
|
||||||
|
(values (cons name last-expr) 'table-function)
|
||||||
|
(lp (make-ast-table-ref src last-expr name))))
|
||||||
|
;; [ ':' NAME ]
|
||||||
|
(if (eq? token #\:)
|
||||||
|
(let* ((name (advance! (single-name))))
|
||||||
|
(values (cons name last-expr) 'table-method))
|
||||||
|
(values last-expr 'function))))
|
||||||
|
(define body (function-body src (eq? type 'table-method) name))
|
||||||
|
(case type
|
||||||
|
((table-function table-method)
|
||||||
|
(make-ast-table-set src (cdr prefix) (make-ast-literal src (symbol->string (car prefix))) body))
|
||||||
|
((function) (make-lua-assignment prefix body))
|
||||||
|
(else (error 'FUNCTION-STATEMENT "should not happen")))))
|
||||||
|
|
||||||
|
;; local-statement -> LOCAL NAME { ',' NAME } [ '=' expression-list ]
|
||||||
|
(define (local-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
;; LOCAL
|
||||||
|
;; (already advanced by calling function)
|
||||||
|
|
||||||
|
(let lp ((names '()))
|
||||||
|
;; NAME
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(set! names (cons token names))
|
||||||
|
(advance!)
|
||||||
|
(if (maybe-skip-next! #\,)
|
||||||
|
;; { ',' NAME }
|
||||||
|
(lp names)
|
||||||
|
(begin
|
||||||
|
(for-each (lambda (n) (environment-define! n 'local)) names)
|
||||||
|
(if (maybe-skip-next! #:=)
|
||||||
|
;; [ '=' expression-list ]
|
||||||
|
(let* ((left (map (lambda (x) (resolve-ref src x)) names))
|
||||||
|
(right (expression-list)))
|
||||||
|
(parse-assignment src left (reverse! right)))
|
||||||
|
;; otherwise, it's not a declaration, not an assignment, and evaluates to nothing
|
||||||
|
*void-literal*)))))
|
||||||
|
|
||||||
|
(define (local-function-statement)
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(let* ((name token))
|
||||||
|
(environment-define! name 'local)
|
||||||
|
(advance!)
|
||||||
|
(make-ast-local-set (get-source-info) name (environment-lookup-gensym name) (function-body))))
|
||||||
|
|
||||||
|
;; for-body
|
||||||
|
(define (for-body)
|
||||||
|
(enforce-next! #:do)
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
(enforce-next! #:end)
|
||||||
|
body))
|
||||||
|
|
||||||
|
;; numeric-for -> FOR NAME '=' expression ',' expression ',' expression DO chunk END
|
||||||
|
(define (numeric-for src name)
|
||||||
|
(define step *default-for-step*)
|
||||||
|
(advance!)
|
||||||
|
(enforce-next! #:=)
|
||||||
|
(enter-environment!)
|
||||||
|
(environment-define! name 'local)
|
||||||
|
(let* ((initial (expression)))
|
||||||
|
(enforce-next! #\,)
|
||||||
|
(let* ((limit (expression)))
|
||||||
|
(when (eq? token #\,)
|
||||||
|
(advance!)
|
||||||
|
(set! step (expression)))
|
||||||
|
(let* ((result (make-ast-numeric-for-loop src name initial limit step (for-body))))
|
||||||
|
(leave-environment!)
|
||||||
|
result))))
|
||||||
|
|
||||||
|
;; list-for -> FOR NAME { ',' NAME } IN expression-list DO chunk END
|
||||||
|
(define (list-for src name)
|
||||||
|
(let* ((names
|
||||||
|
(let lp ((names (list name)))
|
||||||
|
(advance!)
|
||||||
|
(if (eq? token #\,)
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(lp (cons token names)))
|
||||||
|
(reverse! names)))))
|
||||||
|
(enforce-next! #:in)
|
||||||
|
(let* ((exps (expression-list)))
|
||||||
|
(enforce-next! #:do)
|
||||||
|
(for-each
|
||||||
|
(lambda (name)
|
||||||
|
(environment-define! name 'hidden))
|
||||||
|
names)
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
(enforce-next! #:end)
|
||||||
|
(make-ast-list-for-loop src names (map environment-lookup-gensym names) exps body)))))
|
||||||
|
|
||||||
|
;; for-statement -> FOR (numeric-for | list-for) END
|
||||||
|
(define (for-statement)
|
||||||
|
(define src (get-source-info))
|
||||||
|
(enforce-next! #:for)
|
||||||
|
(assert-token-type 'NAME)
|
||||||
|
(let* ((name token)
|
||||||
|
(result
|
||||||
|
(begin
|
||||||
|
(lookahead!)
|
||||||
|
(if (eq? token2 #:=)
|
||||||
|
(numeric-for src name)
|
||||||
|
(if (or-eqv? token2 #:in #\,)
|
||||||
|
(list-for src name)
|
||||||
|
(syntax-error src "expected = or in after for variable"))))))
|
||||||
|
result))
|
||||||
|
|
||||||
|
;; break-statement -> BREAK
|
||||||
|
(define (break-statement)
|
||||||
|
(enforce-next! #:break)
|
||||||
|
(make-ast-break (get-source-info)))
|
||||||
|
|
||||||
|
;; statement
|
||||||
|
(define (statement)
|
||||||
|
(case token
|
||||||
|
((#\;) (advance!) (statement))
|
||||||
|
;; statement -> return
|
||||||
|
((#:return #:break)
|
||||||
|
(values
|
||||||
|
#t
|
||||||
|
(case token
|
||||||
|
((#:return) (return-statement))
|
||||||
|
((#:break) (break-statement)))))
|
||||||
|
((#:if #:function #:do #:while #:repeat #:local #:for)
|
||||||
|
(values
|
||||||
|
#f
|
||||||
|
(case token
|
||||||
|
((#:repeat) (repeat-statement))
|
||||||
|
((#:while) (while-statement))
|
||||||
|
((#:if) (if-statement))
|
||||||
|
((#:function) (function-statement))
|
||||||
|
((#:local)
|
||||||
|
(advance!)
|
||||||
|
(if (maybe-skip-next! #:function)
|
||||||
|
(local-function-statement)
|
||||||
|
(local-statement)))
|
||||||
|
((#:for) (for-statement))
|
||||||
|
((#:do)
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(let* ((body (chunk)))
|
||||||
|
(enforce-next! #:end)
|
||||||
|
body))))))
|
||||||
|
;; statement -> function | assignment
|
||||||
|
(else (values #f (expression-statement)))))
|
||||||
|
|
||||||
|
;; chunk -> { statement [ ';' ] }
|
||||||
|
(define (chunk)
|
||||||
|
(define src (get-source-info))
|
||||||
|
(let loop ((is-last (end-of-chunk? token))
|
||||||
|
(tree '()))
|
||||||
|
(if is-last
|
||||||
|
(begin
|
||||||
|
(maybe-skip-next! #\;)
|
||||||
|
(wrap-expression-in-environment
|
||||||
|
src
|
||||||
|
environment
|
||||||
|
(make-ast-sequence src (reverse! tree))))
|
||||||
|
(receive
|
||||||
|
(is-last node)
|
||||||
|
(statement)
|
||||||
|
(loop (or (end-of-chunk? token) is-last) (cons node tree))))))
|
||||||
|
|
||||||
|
(initialize-lua-lexer! port get-source-info lexer)
|
||||||
|
|
||||||
|
;; toplevel local environment
|
||||||
|
(enter-environment!)
|
||||||
|
;; read first token
|
||||||
|
(advance!)
|
||||||
|
;; return parser
|
||||||
|
chunk)
|
||||||
|
|
||||||
|
(define (read-lua port)
|
||||||
|
(define parser (make-parser port))
|
||||||
|
(parser))
|
606
module/language/lua/runtime.scm
Normal file
606
module/language/lua/runtime.scm
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
;;; Guile Lua --- runtime functionality
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua runtime)
|
||||||
|
#:use-module (language lua common)
|
||||||
|
|
||||||
|
#:use-module (rnrs control)
|
||||||
|
#:use-module ((srfi srfi-1) #:select (filter!))
|
||||||
|
#:use-module (srfi srfi-9)
|
||||||
|
#:use-module (srfi srfi-69)
|
||||||
|
#:use-module ((srfi srfi-98) #:select (get-environment-variable))
|
||||||
|
#:use-module ((system base compile) #:select (compile read-and-compile))
|
||||||
|
|
||||||
|
#:export (
|
||||||
|
runtime-error
|
||||||
|
|
||||||
|
;; semantics
|
||||||
|
false? true?
|
||||||
|
|
||||||
|
;; misc
|
||||||
|
value-type->string
|
||||||
|
assert-type
|
||||||
|
assert-table
|
||||||
|
assert-string
|
||||||
|
assert-number
|
||||||
|
|
||||||
|
;; tables
|
||||||
|
make-table
|
||||||
|
table?
|
||||||
|
table-slots
|
||||||
|
table-metatable
|
||||||
|
table-metatable!
|
||||||
|
table-length
|
||||||
|
|
||||||
|
;; userdata
|
||||||
|
userdata
|
||||||
|
userdata-metatable
|
||||||
|
register-userdata!
|
||||||
|
|
||||||
|
;; metatable
|
||||||
|
might-have-metatable?
|
||||||
|
get-metatable
|
||||||
|
dispatch-metatable-event
|
||||||
|
|
||||||
|
;; table interaction
|
||||||
|
index
|
||||||
|
new-index!
|
||||||
|
get-field
|
||||||
|
|
||||||
|
;; operators
|
||||||
|
len unm eq lt le gt ge add sub mul div pow
|
||||||
|
neq concat
|
||||||
|
|
||||||
|
;; modules
|
||||||
|
make-module-table
|
||||||
|
|
||||||
|
;; global environment
|
||||||
|
*global-env-table*
|
||||||
|
save-fenv
|
||||||
|
check-global-function
|
||||||
|
)
|
||||||
|
|
||||||
|
#:export-syntax (table-slots table? table-metatable table-metatable!)
|
||||||
|
|
||||||
|
) ; define-module
|
||||||
|
|
||||||
|
;; Local Variables:
|
||||||
|
;; eval: (put 'define-global 'scheme-indent-function 1)
|
||||||
|
;; End:
|
||||||
|
|
||||||
|
(define (runtime-error string . arguments)
|
||||||
|
"Throw an error tagged with 'lua-runtime"
|
||||||
|
(throw 'lua-runtime (apply format (cons (string-append "LUA: ERROR: " string "\n") arguments))))
|
||||||
|
|
||||||
|
(define (runtime-warning string . arguments)
|
||||||
|
(apply format (cons #t (cons (string-append "LUA: RUNTIME WARNING: " string "\n") arguments))))
|
||||||
|
|
||||||
|
;;;;; SEMANTICS
|
||||||
|
|
||||||
|
(define (false? x)
|
||||||
|
"Wrapper for Scheme's false semantics that considers #nil to be false"
|
||||||
|
(or (eq? x #f) (eq? x #nil)))
|
||||||
|
|
||||||
|
(define (true? x)
|
||||||
|
"Inversion of false?"
|
||||||
|
(not (false? x)))
|
||||||
|
|
||||||
|
;;;;; MISCELLANEOUS
|
||||||
|
|
||||||
|
(define (value-type->string x)
|
||||||
|
(cond ((table? x) "table")
|
||||||
|
((string? x) "string")
|
||||||
|
((number? x) "number")
|
||||||
|
((boolean? x) "boolean")
|
||||||
|
((eq? x #nil) "nil")
|
||||||
|
((procedure? x) "function")
|
||||||
|
;; TODO: value-type->string must recognize threads
|
||||||
|
(else "userdata")))
|
||||||
|
|
||||||
|
(define (assert-type argument caller expected value predicate)
|
||||||
|
(if (not (predicate value))
|
||||||
|
(runtime-error (format #f "bad argument ~a to '~a' (~a expected, got ~a)" argument caller expected (value-type->string value)))))
|
||||||
|
|
||||||
|
(define-syntax define-assert
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ name string predicate)
|
||||||
|
(define (name argument caller value) (assert-type argument caller string value predicate)))))
|
||||||
|
|
||||||
|
(define-assert assert-table "table" table?)
|
||||||
|
(define-assert assert-string "string" string?)
|
||||||
|
(define-assert assert-number "number" number?)
|
||||||
|
|
||||||
|
;;;;; TABLES
|
||||||
|
|
||||||
|
(define-record-type table
|
||||||
|
(%make-table metatable slots)
|
||||||
|
table?
|
||||||
|
(metatable table-metatable table-metatable!)
|
||||||
|
(slots table-slots))
|
||||||
|
|
||||||
|
(define (make-table)
|
||||||
|
(%make-table #f (make-hash-table)))
|
||||||
|
|
||||||
|
(define (table? x) (table? x))
|
||||||
|
(define (table-metatable x) (table-metatable x))
|
||||||
|
(define (table-metatable! x y) (table-metatable! x y))
|
||||||
|
|
||||||
|
;;;;; USERDATA
|
||||||
|
|
||||||
|
;; Userdata is tracked by this property. It can be #f, indicating the
|
||||||
|
;; object is not userdata, #t, indicating the object is userdata but has
|
||||||
|
;; no metatable, or an actual table, which counts as the metatable.
|
||||||
|
(define userdata-property (make-object-property))
|
||||||
|
|
||||||
|
(define userdata? userdata-property)
|
||||||
|
(define (userdata-metatable x)
|
||||||
|
(and (table? (userdata-property x)) (userdata-property x)))
|
||||||
|
|
||||||
|
(define* (register-userdata! x #:optional metatable)
|
||||||
|
(set! (userdata? x) (or metatable #t)))
|
||||||
|
|
||||||
|
;;;;; METATABLES
|
||||||
|
|
||||||
|
(define (might-have-metatable? x)
|
||||||
|
(or (table? x) (userdata? x)))
|
||||||
|
|
||||||
|
(define (get-metatable x)
|
||||||
|
(cond ((table? x) (table-metatable x))
|
||||||
|
((userdata? x) (userdata-metatable x))
|
||||||
|
(else #f)))
|
||||||
|
|
||||||
|
;;;;; TABLE INTERACTION
|
||||||
|
|
||||||
|
(define (dispatch-metatable-event key default x . arguments)
|
||||||
|
(let* ((metatable (get-metatable x)))
|
||||||
|
(apply
|
||||||
|
(if metatable
|
||||||
|
(hash-table-ref/default (table-slots metatable) key default)
|
||||||
|
default)
|
||||||
|
arguments)))
|
||||||
|
|
||||||
|
;; see manual section 2.5.5
|
||||||
|
(define (table-length table)
|
||||||
|
(let* ((numeric-keys (sort! (filter! number? (hash-table-keys (table-slots table))) <)))
|
||||||
|
(if (eq? (car numeric-keys) 1)
|
||||||
|
(let lp ((cell (car numeric-keys))
|
||||||
|
(rest (cdr numeric-keys))
|
||||||
|
(length 0))
|
||||||
|
;; length does not count "holes"
|
||||||
|
;; so if a table has the keys 1,2,3 and 5, the length of the table is 3
|
||||||
|
(if (or (> cell (+ length 1)) (null? rest))
|
||||||
|
(+ length 1) ;; add one to length as though we had started from one
|
||||||
|
(lp (car rest) (cdr rest) cell)))
|
||||||
|
0)))
|
||||||
|
|
||||||
|
(define (index table key)
|
||||||
|
(dispatch-metatable-event
|
||||||
|
"__index"
|
||||||
|
(lambda (table key) (hash-table-ref/default (table-slots table) key #nil))
|
||||||
|
table
|
||||||
|
table key))
|
||||||
|
|
||||||
|
(define (new-index! table key value)
|
||||||
|
(dispatch-metatable-event
|
||||||
|
"__newindex"
|
||||||
|
(lambda (table key value) (hash-table-set! (table-slots table) key value))
|
||||||
|
table
|
||||||
|
table key value))
|
||||||
|
|
||||||
|
(define* (get-field table key #:optional (default #nil))
|
||||||
|
(define result (index table key))
|
||||||
|
(if (eq? result #nil)
|
||||||
|
default
|
||||||
|
result))
|
||||||
|
|
||||||
|
;;;;; OPERATORS
|
||||||
|
(define (len a)
|
||||||
|
"A function backing the unary # (length) operator"
|
||||||
|
(cond ((string? a) (string-length a))
|
||||||
|
((table? a) (table-length a))
|
||||||
|
(else (runtime-error "attempt to get length of a ~A value" (value-type->string a)))))
|
||||||
|
|
||||||
|
(define (unm a)
|
||||||
|
"A function backing the unary - (negation) operator"
|
||||||
|
(if (might-have-metatable? a)
|
||||||
|
(dispatch-metatable-event "__unm" - a a)
|
||||||
|
(- a)))
|
||||||
|
|
||||||
|
(define (builtin-eq a b)
|
||||||
|
"A function backing the == operator"
|
||||||
|
(equal? a b))
|
||||||
|
|
||||||
|
(define (builtin-concat a b)
|
||||||
|
(when (or (table? a) (table? b))
|
||||||
|
(runtime-error "attempt to concatenate a table value"))
|
||||||
|
(when (or (eq? a #nil) (eq? b #nil))
|
||||||
|
(runtime-error "attempt to concatenate a nil value"))
|
||||||
|
(when (or (boolean? a) (boolean? b))
|
||||||
|
(runtime-error "attempt to concatenate a boolean value"))
|
||||||
|
(format #f "~a~a" a b))
|
||||||
|
|
||||||
|
(define (neq a b)
|
||||||
|
"An inversion of eq"
|
||||||
|
(not (eq a b)))
|
||||||
|
|
||||||
|
(define (ge a b)
|
||||||
|
"A function backing the >= (greater-than-or-equal-to) operator"
|
||||||
|
(not (lt a b)))
|
||||||
|
|
||||||
|
(define (gt a b)
|
||||||
|
"A function backing the > (greater-than) operator"
|
||||||
|
(not (le a b)))
|
||||||
|
|
||||||
|
;; This macro could be even cooler and generate the slot names as well as the
|
||||||
|
;; parsers name/function mappings at expand-time
|
||||||
|
(letrec-syntax
|
||||||
|
((define-binary-operators
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ (name slot-name default) ...)
|
||||||
|
(begin
|
||||||
|
(define-binary-operators () name slot-name default)
|
||||||
|
...))
|
||||||
|
((_ () name slot-name default)
|
||||||
|
(begin
|
||||||
|
(define (name a b)
|
||||||
|
(cond ((might-have-metatable? a)
|
||||||
|
(dispatch-metatable-event slot-name default a a b))
|
||||||
|
((might-have-metatable? b)
|
||||||
|
(dispatch-metatable-event slot-name default b a b))
|
||||||
|
(else (default a b)))))))))
|
||||||
|
(define-binary-operators
|
||||||
|
(add "__add" +)
|
||||||
|
(sub "__sub" -)
|
||||||
|
(mul "__mul" *)
|
||||||
|
(div "__div" /)
|
||||||
|
(pow "__pow" expt)
|
||||||
|
(le "__le" <=)
|
||||||
|
(lt "__lt" <)
|
||||||
|
(eq "__eq" builtin-eq)
|
||||||
|
(concat "__concat" builtin-concat)))
|
||||||
|
|
||||||
|
;;;;; MODULES
|
||||||
|
|
||||||
|
;; A metatable for tables backed by modules
|
||||||
|
(define module-metatable (make-table))
|
||||||
|
|
||||||
|
(hash-table-set!
|
||||||
|
(table-slots module-metatable) "__index"
|
||||||
|
(lambda (table key)
|
||||||
|
(define slots (table-slots table))
|
||||||
|
(if (hash-table-exists? slots key)
|
||||||
|
(hash-table-ref slots key)
|
||||||
|
(let ((key (string->symbol key))
|
||||||
|
(module (hash-table-ref slots 'module)))
|
||||||
|
(if (not (module-defined? module key))
|
||||||
|
#nil
|
||||||
|
(module-ref module key #f))))))
|
||||||
|
|
||||||
|
(define (make-module-table name)
|
||||||
|
(define table (make-table))
|
||||||
|
(table-metatable! table module-metatable)
|
||||||
|
(hash-table-set! (table-slots table) 'module (resolve-module name))
|
||||||
|
table)
|
||||||
|
|
||||||
|
;;;;; GLOBAL ENVIRONMENT
|
||||||
|
|
||||||
|
(define *global-env-table* (make-table))
|
||||||
|
|
||||||
|
;; Saves _G and returns a function that will restore it
|
||||||
|
(define (save-fenv table)
|
||||||
|
"Saves *global-env-table* and returns a function to restore it"
|
||||||
|
(let* ((save *global-env-table*))
|
||||||
|
(set! *global-env-table* table)
|
||||||
|
(lambda ()
|
||||||
|
(set! *global-env-table* save))))
|
||||||
|
|
||||||
|
(define (check-global-function name value)
|
||||||
|
(when (eq? value #nil)
|
||||||
|
(runtime-error "attempt to call global '~a' (a nil value)" name)))
|
||||||
|
|
||||||
|
;;;;; BUILT-INS
|
||||||
|
|
||||||
|
(define-syntax define-global
|
||||||
|
(syntax-rules (do-not-export)
|
||||||
|
((_ (do-not-export name) value)
|
||||||
|
(begin
|
||||||
|
(define name value)
|
||||||
|
(new-index! *global-env-table* (symbol->string 'name) name)))
|
||||||
|
((_ (name . rest) body ...)
|
||||||
|
(define-global name (lambda rest body ...)))
|
||||||
|
((_ name value)
|
||||||
|
(begin
|
||||||
|
(define name value)
|
||||||
|
(export name)
|
||||||
|
(new-index! *global-env-table* (symbol->string 'name) name)))))
|
||||||
|
|
||||||
|
(define-global (assert v . opts)
|
||||||
|
(define message (if (null? opts) "assertion failed" (car opts)))
|
||||||
|
(if (false? v)
|
||||||
|
(runtime-error message)
|
||||||
|
(apply values (cons v opts))))
|
||||||
|
|
||||||
|
;; NOTE: collectgarbage cannot be fully implemented because it expects
|
||||||
|
;; an incremental garbage collector that matches lua's interface; libgc
|
||||||
|
;; can be incremental but i don't think we can turn that on from guile
|
||||||
|
;; currently, and even if we could i'm not sure that libgc exposes what
|
||||||
|
;; lua wants
|
||||||
|
(define-global collectgarbage
|
||||||
|
(lambda* (opt #:optional (arg #nil))
|
||||||
|
(define (ignore) (runtime-warning "collectgarbage cannot respect command ~a" opt))
|
||||||
|
(assert-type 1 "collectgarbage" "string" opt string?)
|
||||||
|
(cond ((string=? opt "stop") (ignore))
|
||||||
|
((string=? opt "restart") (ignore))
|
||||||
|
((string=? opt "collect") (gc))
|
||||||
|
((string=? opt "count") (ignore))
|
||||||
|
((string=? opt "step") (ignore))
|
||||||
|
((string=? opt "setpause") (ignore))
|
||||||
|
((string=? opt "setstepmul") (ignore))
|
||||||
|
(else (runtime-error "bad argument #1 to 'collectgarbage' (invalid option ~a)" opt)))))
|
||||||
|
|
||||||
|
(define-global (dofile filename)
|
||||||
|
(assert-string 1 "dofile" filename)
|
||||||
|
(runtime-warning "dofile cannot return the values of the chunk and instead will return #nil")
|
||||||
|
(call-with-input-file filename
|
||||||
|
(lambda (file)
|
||||||
|
(compile ((@ (language lua parser) read-lua) file) #:from 'lua #:to 'value)))
|
||||||
|
#nil)
|
||||||
|
|
||||||
|
(define-global (do-not-export error)
|
||||||
|
(lambda* (message #:optional level)
|
||||||
|
(runtime-warning "level argument to error is not respected")
|
||||||
|
(throw 'lua-error message)))
|
||||||
|
|
||||||
|
;; global variable table
|
||||||
|
(define-global _G *global-env-table*)
|
||||||
|
|
||||||
|
(define-global (getmetatable table)
|
||||||
|
(assert-table 1 "getmetatable" table)
|
||||||
|
(let* ((mt (table-metatable table)))
|
||||||
|
(if (eq? mt #f)
|
||||||
|
#nil
|
||||||
|
mt)))
|
||||||
|
|
||||||
|
(define-global (ipairs table)
|
||||||
|
(assert-table 1 "ipairs" table)
|
||||||
|
(values
|
||||||
|
(lambda (table indice)
|
||||||
|
(set! indice (+ indice 1))
|
||||||
|
(let* ((value (index table indice)))
|
||||||
|
(if (eq? value #nil)
|
||||||
|
(values #nil #nil)
|
||||||
|
(values indice value)))
|
||||||
|
)
|
||||||
|
table
|
||||||
|
0))
|
||||||
|
|
||||||
|
(define (load-warning)
|
||||||
|
(runtime-warning "load, loadfile, and loadstring cannot return the results of evaluating a file"))
|
||||||
|
|
||||||
|
(define (load-chunkname-warning chunkname)
|
||||||
|
(when chunkname
|
||||||
|
(runtime-warning "load and loadstring ignore chunkname")))
|
||||||
|
|
||||||
|
(define-global load
|
||||||
|
(lambda* (func #:optional chunkname)
|
||||||
|
(load-warning)
|
||||||
|
(load-chunkname-warning chunkname)
|
||||||
|
(lambda ()
|
||||||
|
(compile
|
||||||
|
((@ (language lua parser) read-lua)
|
||||||
|
(open-input-string
|
||||||
|
(let lp ((tree '())
|
||||||
|
(result (func)))
|
||||||
|
(if (or (equal? func "") (eq? func #nil) (eq? func *unspecified*))
|
||||||
|
(string-concatenate-reverse tree)
|
||||||
|
(lp (cons func tree) (func))))))
|
||||||
|
#:from 'lua #:to 'value))))
|
||||||
|
|
||||||
|
(define-global loadfile
|
||||||
|
(lambda* (#:optional filename)
|
||||||
|
(load-warning)
|
||||||
|
(lambda ()
|
||||||
|
(if filename
|
||||||
|
(call-with-input-file filename
|
||||||
|
(lambda (file)
|
||||||
|
(compile ((@ (language lua parser) read-lua) file) #:from 'lua #:to 'value)))
|
||||||
|
(read-and-compile (current-input-port) #:from 'lua)))))
|
||||||
|
|
||||||
|
(define-global loadstring
|
||||||
|
(lambda* (string #:optional chunkname)
|
||||||
|
(load-warning)
|
||||||
|
(load-chunkname-warning chunkname)
|
||||||
|
(lambda ()
|
||||||
|
(compile ((@ (language lua parser) read-lua) (open-input-string string)) #:from 'lua #:to 'value))))
|
||||||
|
|
||||||
|
;; TODO: module
|
||||||
|
|
||||||
|
(define-global next
|
||||||
|
(lambda* (table #:optional (index #nil))
|
||||||
|
(assert-table 1 "next" table)
|
||||||
|
(let* ((keys (hash-table-keys (table-slots table))))
|
||||||
|
;; empty table = nil
|
||||||
|
(if (null? keys)
|
||||||
|
#nil
|
||||||
|
(begin
|
||||||
|
(if (eq? index #nil)
|
||||||
|
(let* ((next-index (list-ref keys 0)))
|
||||||
|
(values next-index (rawget table next-index)))
|
||||||
|
(let* ((key-ref (+ ((@ (srfi srfi-1) list-index) (lambda (x) (equal? x index)) keys) 1)))
|
||||||
|
(if (>= key-ref (length keys))
|
||||||
|
(values #nil #nil)
|
||||||
|
(let* ((next-index (list-ref keys key-ref)))
|
||||||
|
(values next-index (rawget table next-index)))))))))))
|
||||||
|
|
||||||
|
(define-global pairs
|
||||||
|
(lambda* (table)
|
||||||
|
(values next table #nil)))
|
||||||
|
|
||||||
|
(define-global (pcall function . arguments)
|
||||||
|
(catch #t
|
||||||
|
(lambda () (apply function arguments))
|
||||||
|
(lambda args (apply values (cons #f args)))))
|
||||||
|
|
||||||
|
(define-global (print . arguments)
|
||||||
|
(for-each
|
||||||
|
(lambda (x)
|
||||||
|
(display (tostring x))
|
||||||
|
(write-char #\tab))
|
||||||
|
arguments)
|
||||||
|
(newline)
|
||||||
|
#nil)
|
||||||
|
|
||||||
|
(define-global (rawequal v1 v2)
|
||||||
|
(equal? v1 v2))
|
||||||
|
|
||||||
|
(define-global (rawget table key)
|
||||||
|
(assert-table 1 "rawget" table)
|
||||||
|
(hash-table-ref (table-slots table) key))
|
||||||
|
|
||||||
|
(define-global (rawset table key value)
|
||||||
|
(assert-table 1 "rawset" table)
|
||||||
|
(hash-table-set! (table-slots table) key value))
|
||||||
|
|
||||||
|
(define-global (select index . rest)
|
||||||
|
(define rest-length (length rest))
|
||||||
|
(cond ((number? index)
|
||||||
|
(let lp ((vals '())
|
||||||
|
(i index))
|
||||||
|
(if (> i rest-length)
|
||||||
|
(apply values (reverse! vals))
|
||||||
|
(lp (cons (list-ref rest (- i 1)) vals) (+ i 1)))))
|
||||||
|
(else rest-length)))
|
||||||
|
|
||||||
|
(define-global (setmetatable table metatable)
|
||||||
|
(assert-table 1 "setmetatable" table)
|
||||||
|
(assert-type 2 "setmetatable" "nil or table" metatable (lambda (x) (or (table? x) (eq? x #nil))))
|
||||||
|
(table-metatable! table (if (eq? metatable #nil) #f metatable))
|
||||||
|
table)
|
||||||
|
|
||||||
|
;; NOTE: built-in 'tonumber' is implemented on string->number and may
|
||||||
|
;; not have the same semantics as lua's tonumber; it should be based on the lexer
|
||||||
|
(define-global tonumber
|
||||||
|
(lambda* (e #:optional (base 10))
|
||||||
|
(cond ((number? e) e)
|
||||||
|
((string? e)
|
||||||
|
(unless (or-eqv? base 2 8 10 16)
|
||||||
|
(runtime-warning "tonumber cannot respect bases other than 2, 8, 10, and 16"))
|
||||||
|
(string->number e base))
|
||||||
|
(else #nil))))
|
||||||
|
|
||||||
|
(define-global (tostring e)
|
||||||
|
(cond ((string? e) e)
|
||||||
|
((eqv? e #t) "true")
|
||||||
|
((eqv? e #f) "false")
|
||||||
|
((eqv? e #nil) "nil")
|
||||||
|
((number? e) (number->string e))
|
||||||
|
((might-have-metatable? e)
|
||||||
|
(dispatch-metatable-event
|
||||||
|
"__tostring"
|
||||||
|
(lambda (table) (format #f "~A" e))
|
||||||
|
e
|
||||||
|
e))
|
||||||
|
(else (runtime-error "tostring cannot convert value ~A" e))))
|
||||||
|
|
||||||
|
(define-global (type v)
|
||||||
|
(value-type->string v))
|
||||||
|
|
||||||
|
(define-global unpack
|
||||||
|
(lambda* (array #:optional (i 1) j)
|
||||||
|
(assert-table 1 "unpack" array)
|
||||||
|
(unless j (set! j (table-length array)))
|
||||||
|
(apply values (reverse!
|
||||||
|
(let lp ((ls '())
|
||||||
|
(i i))
|
||||||
|
(if (> i j)
|
||||||
|
ls
|
||||||
|
(if (eq? #nil (index array i))
|
||||||
|
ls
|
||||||
|
(lp (cons (index array i) ls) (+ i 1)))))))))
|
||||||
|
|
||||||
|
;; _VERSION
|
||||||
|
;; contains a string describing the lua version
|
||||||
|
(define-global _VERSION "Guile/Lua 5.1")
|
||||||
|
|
||||||
|
(define-global (xpcall f err)
|
||||||
|
(catch #t
|
||||||
|
(lambda () (values #t (f)))
|
||||||
|
(lambda args (values #f (err args)))))
|
||||||
|
|
||||||
|
;;; MODULE SYSTEM
|
||||||
|
|
||||||
|
;; package
|
||||||
|
(define-global package (make-table))
|
||||||
|
|
||||||
|
;; package.cpath
|
||||||
|
(new-index! package "cpath" (or (get-environment-variable "LUA_CPATH")
|
||||||
|
"./?.so;/usr/lib/lua/5.1/?.so;/usr/lib/lua/5.1/loadall.so"))
|
||||||
|
;; package.loaded
|
||||||
|
(define loaded (make-table))
|
||||||
|
(new-index! package "loaded" loaded)
|
||||||
|
|
||||||
|
;; package.loaders
|
||||||
|
(define loaders (make-table))
|
||||||
|
(new-index! package "loaders" loaders)
|
||||||
|
|
||||||
|
;; package.loadlib
|
||||||
|
(new-index! package "loadlib" (lambda (lib func . _) (runtime-error "loadlib not implemented")))
|
||||||
|
|
||||||
|
;; package.path
|
||||||
|
(new-index! package "path" (or (get-environment-variable "LUA_PATH") "./?.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;/usr/lib/lua/5.1/?.lua;/usr/lib/lua/5.1/?/init.lua"))
|
||||||
|
|
||||||
|
;; package.preload
|
||||||
|
(define preload (make-table))
|
||||||
|
(new-index! package "preload" preload)
|
||||||
|
|
||||||
|
;; package.seeall
|
||||||
|
(new-index! package "seeall" (lambda (module . _) (runtime-error "seeall unimplemented")))
|
||||||
|
|
||||||
|
;; arg
|
||||||
|
;; command line argument table
|
||||||
|
(define arg (make-table))
|
||||||
|
(let lp ((rest (command-line))
|
||||||
|
(i 0))
|
||||||
|
(new-index! arg i (car rest))
|
||||||
|
(if (not (null? (cdr rest)))
|
||||||
|
(lp (cdr rest) (1+ i))))
|
||||||
|
|
||||||
|
;; require
|
||||||
|
(define (register-loaded-module name table)
|
||||||
|
(rawset *global-env-table* name table)
|
||||||
|
(rawset loaded name table))
|
||||||
|
|
||||||
|
(define (module-exists? name)
|
||||||
|
(if (module-public-interface (resolve-module name))
|
||||||
|
#t
|
||||||
|
#f))
|
||||||
|
|
||||||
|
(define-global (require module-name . _)
|
||||||
|
(assert-type 1 "require" "string" module-name string?)
|
||||||
|
;; try to load module, if it's not already loaded
|
||||||
|
(if (not (hash-table-exists? (table-slots loaded) module-name))
|
||||||
|
(let* ((std-module-name `(language lua standard ,(string->symbol module-name))))
|
||||||
|
(if (module-exists? std-module-name)
|
||||||
|
(register-loaded-module module-name (make-module-table std-module-name)))))
|
||||||
|
|
||||||
|
(if (not (hash-table-exists? (table-slots loaded) module-name))
|
||||||
|
(runtime-error "require failed"))
|
||||||
|
(rawget loaded module-name))
|
||||||
|
|
33
module/language/lua/spec.scm
Normal file
33
module/language/lua/spec.scm
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
;;; Guile Lua --- language specification
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua spec)
|
||||||
|
#:use-module (system base language)
|
||||||
|
#:use-module (language lua parser)
|
||||||
|
#:use-module (language lua compile-tree-il)
|
||||||
|
#:export (lua))
|
||||||
|
|
||||||
|
(define-language lua
|
||||||
|
#:title "Lua"
|
||||||
|
#:reader (lambda (port _)
|
||||||
|
(if (file-port? port)
|
||||||
|
(read-lua port)))
|
||||||
|
#:compilers `((tree-il . ,compile-tree-il))
|
||||||
|
#:printer write)
|
185
module/language/lua/standard/io.scm
Normal file
185
module/language/lua/standard/io.scm
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
;;; Guile Lua --- io standard library
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua standard io)
|
||||||
|
#:use-module (language lua runtime)
|
||||||
|
|
||||||
|
#:use-module (ice-9 popen)
|
||||||
|
#:use-module (ice-9 rdelim)
|
||||||
|
#:use-module (rnrs control))
|
||||||
|
|
||||||
|
;; io.file:read
|
||||||
|
|
||||||
|
;; metatable for file objects
|
||||||
|
(define file (make-table))
|
||||||
|
|
||||||
|
(rawset file '__index
|
||||||
|
(lambda (self key)
|
||||||
|
(rawget file key)))
|
||||||
|
|
||||||
|
(define stdin (current-input-port))
|
||||||
|
(define stdout (current-output-port))
|
||||||
|
(define stderr (current-error-port))
|
||||||
|
|
||||||
|
(define* (close #:optional (file stdout))
|
||||||
|
(close-port file))
|
||||||
|
|
||||||
|
(rawset file 'close
|
||||||
|
(lambda (self)
|
||||||
|
(close self)))
|
||||||
|
|
||||||
|
;; lua doesn't actually have an optional flush argument, but this is more in line with everything else
|
||||||
|
(define* (flush #:optional (file stdout))
|
||||||
|
(force-output file))
|
||||||
|
|
||||||
|
(rawset file 'flush
|
||||||
|
(lambda (self)
|
||||||
|
(flush self)))
|
||||||
|
|
||||||
|
(define* (input #:optional filename)
|
||||||
|
(if filename
|
||||||
|
(let* ((file (open filename)))
|
||||||
|
(set! stdin file)
|
||||||
|
file)
|
||||||
|
stdin))
|
||||||
|
|
||||||
|
(define (line-iterator file auto-close?)
|
||||||
|
(lambda ()
|
||||||
|
(let* ((line (read-line file)))
|
||||||
|
(if (eof-object? line)
|
||||||
|
(begin
|
||||||
|
(if auto-close?
|
||||||
|
(close-port file))
|
||||||
|
#nil)
|
||||||
|
line))))
|
||||||
|
|
||||||
|
(define* (lines #:optional filename)
|
||||||
|
(let* ((file (if filename (open filename) stdin)))
|
||||||
|
(line-iterator file (and filename))))
|
||||||
|
|
||||||
|
(rawset file 'lines
|
||||||
|
(lambda (self)
|
||||||
|
(line-iterator self #f)))
|
||||||
|
|
||||||
|
(define* (open filename #:optional (mode "r"))
|
||||||
|
(assert-string 1 "io.open" filename)
|
||||||
|
(assert-string 2 "io.open" mode)
|
||||||
|
(let* ((handle (open-file filename mode)))
|
||||||
|
(register-userdata! handle file)
|
||||||
|
handle))
|
||||||
|
|
||||||
|
(define* (output #:optional filename)
|
||||||
|
(if filename
|
||||||
|
(let* ((file (open filename "w")))
|
||||||
|
(set! stdout file)
|
||||||
|
file)
|
||||||
|
stdout))
|
||||||
|
|
||||||
|
(define* (popen prog #:optional (mode "r"))
|
||||||
|
(assert-string 2 "io.popen" mode)
|
||||||
|
(open-pipe
|
||||||
|
prog
|
||||||
|
(if (string=? mode "w") OPEN_WRITE OPEN_READ)))
|
||||||
|
|
||||||
|
(define (default-read port)
|
||||||
|
(if (eof-object? (peek-char port))
|
||||||
|
#nil
|
||||||
|
(read-line port)))
|
||||||
|
|
||||||
|
(rawset file 'read
|
||||||
|
(lambda (self . formats)
|
||||||
|
(if (null? formats)
|
||||||
|
(default-read self)
|
||||||
|
(apply
|
||||||
|
values
|
||||||
|
(map
|
||||||
|
(lambda (self . formats)
|
||||||
|
(unless (or (number? format) (string? format))
|
||||||
|
(runtime-error "'file:read' expects a string or number as format argument, but got ~a" format))
|
||||||
|
(if (number? format)
|
||||||
|
(if (eof-object? (peek-char self))
|
||||||
|
#nil
|
||||||
|
(let lp ((out (open-output-string))
|
||||||
|
(i format))
|
||||||
|
(if (= i 0)
|
||||||
|
(get-output-string out)
|
||||||
|
(let ((c (read-char self)))
|
||||||
|
(if (eof-object? self)
|
||||||
|
(get-output-string out)
|
||||||
|
(begin
|
||||||
|
(write-char c out)
|
||||||
|
(lp out (- i 1))))))))
|
||||||
|
|
||||||
|
(let* ((format-length (string-length format))
|
||||||
|
(c1 (if (> format-length 0) (string-ref format 0) #f))
|
||||||
|
(c2 (if (> format-length 1) (string-ref format 1) #f)))
|
||||||
|
(cond ((eq? c2 #\n) (runtime-error "'file:read' number reading is not yet supported"))
|
||||||
|
((eq? c2 #\a)
|
||||||
|
(if (eof-object? (peek-char self))
|
||||||
|
#nil
|
||||||
|
(let lp ((out (open-output-string)))
|
||||||
|
(let ((c (read-char self)))
|
||||||
|
(if (eof-object? c)
|
||||||
|
(get-output-string out)
|
||||||
|
(begin
|
||||||
|
(write-char c out)
|
||||||
|
(lp out)))))))
|
||||||
|
((eq? c2 #\l)
|
||||||
|
(default-read self))
|
||||||
|
(else
|
||||||
|
(runtime-error "file:read does not understand format ~a" format))))))
|
||||||
|
formats)))))
|
||||||
|
|
||||||
|
(rawset file 'seek
|
||||||
|
(lambda* (self #:optional (whence "cur") (offset 0))
|
||||||
|
(assert-string 1 "file:seek" whence)
|
||||||
|
(assert-number 2 "file:seek" offset)
|
||||||
|
(seek self offset
|
||||||
|
(cond ((string=? whence "cur") SEEK_CUR)
|
||||||
|
((string=? whence "set") SEEK_SET)
|
||||||
|
((string=? whence "end") SEEK_END)
|
||||||
|
(else (runtime-error "invalid 'whence' argument to 'file:seek'; expected \"cur\", \"set\", or \"end\""))))))
|
||||||
|
|
||||||
|
(rawset file 'setvbuf
|
||||||
|
(lambda* (self mode #:optional size)
|
||||||
|
(assert-string 1 "file:setvbuf" mode)
|
||||||
|
(let* ((translated-mode
|
||||||
|
(cond ((string=? mode "no") _IONBF)
|
||||||
|
((string=? mode "line") _IOLBF)
|
||||||
|
((string=? mode "full") _IOFBF))))
|
||||||
|
(if size
|
||||||
|
(setvbuf self mode)
|
||||||
|
(setvbuf self mode size)))))
|
||||||
|
|
||||||
|
(rawset file 'write
|
||||||
|
(lambda* (self . args)
|
||||||
|
(for-each
|
||||||
|
(lambda (arg)
|
||||||
|
(unless (or (string? arg) (number? arg))
|
||||||
|
(runtime-error "'file:write' expects string or number as argument but got '~a'" arg))
|
||||||
|
(display arg self))
|
||||||
|
args)))
|
||||||
|
|
||||||
|
(define (type obj)
|
||||||
|
(if (port? obj)
|
||||||
|
(if (port-closed? obj)
|
||||||
|
"closed"
|
||||||
|
"file")
|
||||||
|
#nil))
|
136
module/language/lua/standard/math.scm
Normal file
136
module/language/lua/standard/math.scm
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
;;; Guile Lua --- math standard library
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua standard math)
|
||||||
|
#:use-module (language lua runtime))
|
||||||
|
|
||||||
|
;; TODO: math.modf
|
||||||
|
;; TODO: math.deg,rad,frexp,random not tested
|
||||||
|
|
||||||
|
;; NOTE: as opposed to lua, math.sqrt accepts negative arguments, as
|
||||||
|
;; guile's numeric tower is capable of representing complex numbers
|
||||||
|
|
||||||
|
(define huge +inf.0)
|
||||||
|
(define *nan* (nan))
|
||||||
|
(define pi 3.14159265358979323846)
|
||||||
|
(define radians_per_degree (/ pi 180.0))
|
||||||
|
|
||||||
|
(letrec-syntax
|
||||||
|
((wrap-builtins
|
||||||
|
(syntax-rules (rename rename2 variable-arity)
|
||||||
|
;; we must know the arity of the wrapped procedure because lua ignores superfluous arguments whereas it is an error in scheme
|
||||||
|
|
||||||
|
;; simple wrap with new name and 1 argument
|
||||||
|
((_ () (rename guile-name lua-name))
|
||||||
|
(define (lua-name a . _)
|
||||||
|
((@ (guile) guile-name) a)))
|
||||||
|
|
||||||
|
((_ () (rename2 guile-name lua-name))
|
||||||
|
(define (lua-name a b . _)
|
||||||
|
((@ (guile) guile-name) a b)))
|
||||||
|
|
||||||
|
;; simple wrap with 2 arguments
|
||||||
|
((_ () (2 name))
|
||||||
|
(define (name a b . _)
|
||||||
|
((@ (guile) name) a b)))
|
||||||
|
|
||||||
|
;; simple wrap with variable arguments
|
||||||
|
((_ () (variable-arity name))
|
||||||
|
(define (name . _)
|
||||||
|
(apply (@ (guile) name) _)))
|
||||||
|
|
||||||
|
;; simple wrap with 1 argument
|
||||||
|
((_ () name)
|
||||||
|
(define (name a . _)
|
||||||
|
((@ (guile) name) a)))
|
||||||
|
|
||||||
|
;; 1) take all input and pass it to subtransformers
|
||||||
|
((_ subform ...)
|
||||||
|
(begin
|
||||||
|
(wrap-builtins () subform)
|
||||||
|
...)))))
|
||||||
|
(wrap-builtins
|
||||||
|
abs
|
||||||
|
acos
|
||||||
|
asin
|
||||||
|
atan
|
||||||
|
(rename ceiling ceil)
|
||||||
|
cos
|
||||||
|
cosh
|
||||||
|
exp
|
||||||
|
(rename2 remainder modf)
|
||||||
|
floor
|
||||||
|
log
|
||||||
|
log10
|
||||||
|
sin
|
||||||
|
sinh
|
||||||
|
sqrt
|
||||||
|
(variable-arity max)
|
||||||
|
(variable-arity min)
|
||||||
|
(rename expt pow)
|
||||||
|
tan
|
||||||
|
tanh))
|
||||||
|
|
||||||
|
(define (atan2 x y)
|
||||||
|
(atan (/ x y)))
|
||||||
|
|
||||||
|
;; copy the global random state for this module so we don't mutate it
|
||||||
|
(define randomstate (copy-random-state *random-state*))
|
||||||
|
|
||||||
|
(define (randomseed seed . _)
|
||||||
|
(set! randomstate (seed->random-state seed)))
|
||||||
|
|
||||||
|
(define* (random #:optional m n #:rest _)
|
||||||
|
;; this can be a little confusing because guile's random number
|
||||||
|
;; generator only allows [0, N) but we need [0,1), [1,m] and [m,n]
|
||||||
|
(cond ((and (not m) (not n)) ((@ (guile) random) 1.0))
|
||||||
|
;; this is really [1,M)
|
||||||
|
((and m) (+ 1 ((@ (guile) random) m)))
|
||||||
|
((and m n) (+ m ((@ (guile) random) n)))
|
||||||
|
(else (error #:RANDOM "should not happen"))))
|
||||||
|
|
||||||
|
(define (deg x)
|
||||||
|
(/ x radians_per_degree))
|
||||||
|
|
||||||
|
(define (rad x)
|
||||||
|
(* x radians_per_degree))
|
||||||
|
|
||||||
|
(define (ldexp x exp)
|
||||||
|
(cond ((= exp 0) x)
|
||||||
|
((= exp *nan*) *nan*)
|
||||||
|
((= exp +inf.0) +inf.0)
|
||||||
|
((= exp -inf.0) -inf.0)
|
||||||
|
(else (* x (expt 2 exp)))))
|
||||||
|
|
||||||
|
(define log2
|
||||||
|
(let ((log2 (log 2)))
|
||||||
|
(lambda (x)
|
||||||
|
(/ (log x) log2))))
|
||||||
|
|
||||||
|
(define (frexp x)
|
||||||
|
(if (zero? x)
|
||||||
|
0.0
|
||||||
|
(let* ((l2 (log2 x))
|
||||||
|
(e (floor (log2 x)))
|
||||||
|
(e (if (= l2 e)
|
||||||
|
(inexact->exact e)
|
||||||
|
(+ (inexact->exact e) 1)))
|
||||||
|
(f (/ x (expt 2 e))))
|
||||||
|
f)))
|
108
module/language/lua/standard/os.scm
Normal file
108
module/language/lua/standard/os.scm
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
;;; Guile Lua --- os standard library
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua standard os)
|
||||||
|
#:use-module (language lua runtime)
|
||||||
|
|
||||||
|
#:use-module (srfi srfi-98))
|
||||||
|
|
||||||
|
(define (clock)
|
||||||
|
(tms:clock (times)))
|
||||||
|
|
||||||
|
(define* (date #:optional (format "%c") time)
|
||||||
|
(let* ((utc? (eq? (string-ref format 0) #\!))
|
||||||
|
;; skip !
|
||||||
|
(format (if utc? (string-copy format 1) format))
|
||||||
|
(stm ((if utc? gmtime localtime) (or time (current-time)))))
|
||||||
|
(if time
|
||||||
|
(begin
|
||||||
|
(assert-number 2 "date" time)
|
||||||
|
(if (string=? format "*t")
|
||||||
|
(let* ((table (make-table)))
|
||||||
|
(rawset table "sec" (tm:sec stm))
|
||||||
|
(rawset table "min" (tm:min stm))
|
||||||
|
(rawset table "hour" (tm:hour stm))
|
||||||
|
(rawset table "month" (+ 1 (tm:mon stm)))
|
||||||
|
(rawset table "year" (+ 1900 (tm:year stm)))
|
||||||
|
(rawset table "wday" (+ 1 (tm:wday stm)))
|
||||||
|
(rawset table "yday" (+ 1 (tm:yday stm)))
|
||||||
|
(rawset table "isdst" (> (tm:isdst stm) 0))
|
||||||
|
table)
|
||||||
|
(strftime format stm)))
|
||||||
|
(strftime format stm))))
|
||||||
|
|
||||||
|
(define (difftime t2 t1)
|
||||||
|
(- t2 t1))
|
||||||
|
|
||||||
|
(define* (execute #:optional command)
|
||||||
|
(if (not command)
|
||||||
|
1
|
||||||
|
(system command)))
|
||||||
|
|
||||||
|
(define* (exit #:optional (code 0))
|
||||||
|
(primitive-exit code))
|
||||||
|
|
||||||
|
(define (getenv varname)
|
||||||
|
(or (get-environment-variable varname) #nil))
|
||||||
|
|
||||||
|
(define rename rename-file)
|
||||||
|
|
||||||
|
(define (remove filename)
|
||||||
|
(if (file-is-directory? filename)
|
||||||
|
(rmdir filename)
|
||||||
|
(delete-file filename)))
|
||||||
|
|
||||||
|
(define* (setlocale locale #:optional (category "all"))
|
||||||
|
(assert-string 2 "setlocale" category)
|
||||||
|
((@ (guile) setlocale)
|
||||||
|
locale
|
||||||
|
(cond ((string=? category "all") LC_ALL)
|
||||||
|
((string=? category "collate") LC_COLLATE)
|
||||||
|
((string=? category "ctype") LC_CTYPE)
|
||||||
|
((string=? category "messages") LC_MESSAGES)
|
||||||
|
((string=? category "monetary") LC_MONETARY)
|
||||||
|
((string=? category "numeric") LC_NUMERIC)
|
||||||
|
((string=? category "time") LC_TIME))))
|
||||||
|
|
||||||
|
(define* (time #:optional table)
|
||||||
|
(if table
|
||||||
|
(begin
|
||||||
|
(assert-table 1 "time" table)
|
||||||
|
(let* ((sec (get-field table "sec" 0))
|
||||||
|
(min (get-field table "min" 0))
|
||||||
|
(hour (get-field table "hour" 12))
|
||||||
|
(day (get-field table "day" -1))
|
||||||
|
(month (- (get-field table "month" -1) 1))
|
||||||
|
(year (- (get-field table "year" -1) 1900))
|
||||||
|
(isdst (get-field table "isdst" 0))
|
||||||
|
(result (make-vector 11 0)))
|
||||||
|
(set-tm:sec result sec)
|
||||||
|
(set-tm:min result min)
|
||||||
|
(set-tm:hour result hour)
|
||||||
|
(set-tm:mday result day)
|
||||||
|
(set-tm:mon result month)
|
||||||
|
(set-tm:year result year)
|
||||||
|
(set-tm:isdst result isdst)
|
||||||
|
(set-tm:zone result "")
|
||||||
|
(car (mktime result)))
|
||||||
|
)
|
||||||
|
(current-time)))
|
||||||
|
|
||||||
|
(define tmpname mkstemp!)
|
103
module/language/lua/standard/table.scm
Normal file
103
module/language/lua/standard/table.scm
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
;;; Guile Lua --- table standard library
|
||||||
|
|
||||||
|
;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;
|
||||||
|
;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;; License as published by the Free Software Foundation; either
|
||||||
|
;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;
|
||||||
|
;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;; Lesser General Public License for more details.
|
||||||
|
;;;
|
||||||
|
;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
;;; Code:
|
||||||
|
|
||||||
|
(define-module (language lua standard table)
|
||||||
|
#:use-module (language lua common)
|
||||||
|
#:use-module (language lua runtime)
|
||||||
|
|
||||||
|
#:use-module (rnrs control)
|
||||||
|
#:use-module ((srfi srfi-1) #:select (filter!))
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (srfi srfi-16)
|
||||||
|
#:use-module ((srfi srfi-69) #:select (hash-table-size hash-table-keys))
|
||||||
|
)
|
||||||
|
|
||||||
|
;; TODO - insert, remove, sort
|
||||||
|
|
||||||
|
(define (add-field! table buffer i)
|
||||||
|
(define string (rawget table i))
|
||||||
|
(unless (string? string)
|
||||||
|
(runtime-error "invalid value (~a) at index ~a in table for concat; expected string" string i))
|
||||||
|
(display string buffer))
|
||||||
|
|
||||||
|
(define* (concat table #:optional (sep "") (i 1) (%last #f) #:rest _)
|
||||||
|
(define buffer (open-output-string))
|
||||||
|
(assert-table 1 "concat" table)
|
||||||
|
(let* ((ht (table-slots table))
|
||||||
|
(last (if (not %last) (table-length table) %last)))
|
||||||
|
(let lp ((i i))
|
||||||
|
(if (< i last)
|
||||||
|
(begin
|
||||||
|
(add-field! table buffer i)
|
||||||
|
(display sep buffer)
|
||||||
|
(lp (+ i 1)))
|
||||||
|
(when (= i last)
|
||||||
|
(add-field! table buffer i)))))
|
||||||
|
(get-output-string buffer))
|
||||||
|
|
||||||
|
;; Arguments are named a1 and a2 because confusingly, the middle argument is optional
|
||||||
|
;; table.insert(table, [pos,] value)
|
||||||
|
(define (insert table . arguments)
|
||||||
|
(assert-table 1 "insert" table)
|
||||||
|
(receive
|
||||||
|
(pos value)
|
||||||
|
(apply
|
||||||
|
(case-lambda
|
||||||
|
((value)
|
||||||
|
(values (table-length table) value))
|
||||||
|
((pos value)
|
||||||
|
(assert-number 1 "insert" pos)
|
||||||
|
(let* ((length (table-length table))
|
||||||
|
(e (if (> pos length) pos length)))
|
||||||
|
(let lp ((i e))
|
||||||
|
(when (> i pos)
|
||||||
|
(rawset table i (rawget table (- i 1)))
|
||||||
|
(lp (- i 1))))
|
||||||
|
(values pos value)))
|
||||||
|
(else
|
||||||
|
(runtime-error "wrong number of arguments to 'insert'")))
|
||||||
|
arguments)
|
||||||
|
(rawset table pos value)))
|
||||||
|
|
||||||
|
(define (maxn table . _)
|
||||||
|
(assert-table 1 "maxn" table)
|
||||||
|
(let* ((result (sort! (filter! number? (hash-table-keys (table-slots table))) >)))
|
||||||
|
(if (null? result)
|
||||||
|
0
|
||||||
|
(car result))))
|
||||||
|
|
||||||
|
(define* (remove table #:optional pos)
|
||||||
|
(assert-table 1 "remove" table)
|
||||||
|
(let* ((e (table-length table)))
|
||||||
|
(unless pos (set! pos (table-length table)))
|
||||||
|
(assert-number 2 "remove" pos)
|
||||||
|
(if (eq? (table-length table) 0)
|
||||||
|
0
|
||||||
|
(let* ((result (rawget table pos)))
|
||||||
|
(let lp ((pos pos))
|
||||||
|
(if (< pos e)
|
||||||
|
(begin
|
||||||
|
(rawset table pos (rawget table (+ pos 1)))
|
||||||
|
(lp (+ pos 1)))
|
||||||
|
(rawset table pos #nil)))
|
||||||
|
result))))
|
||||||
|
|
||||||
|
(define (sort . rest)
|
||||||
|
(runtime-error "table.sort UNIMPLEMENTED"))
|
|
@ -69,6 +69,10 @@ SCM_TESTS = tests/00-initial-env.test \
|
||||||
tests/keywords.test \
|
tests/keywords.test \
|
||||||
tests/list.test \
|
tests/list.test \
|
||||||
tests/load.test \
|
tests/load.test \
|
||||||
|
tests/lua-eval.test \
|
||||||
|
tests/lua-eval-2.test \
|
||||||
|
tests/lua-lexer.test \
|
||||||
|
tests/lua-standard-library.test \
|
||||||
tests/match.test \
|
tests/match.test \
|
||||||
tests/match.test.upstream \
|
tests/match.test.upstream \
|
||||||
tests/modules.test \
|
tests/modules.test \
|
||||||
|
|
112
test-suite/tests/lua-eval-2.test
Normal file
112
test-suite/tests/lua-eval-2.test
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
;;;; lua-eval-2.test --- basic tests for builtin lua constructs, act II -*- mode: scheme -*-
|
||||||
|
;;;;
|
||||||
|
;;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;;; License as published by the Free Software Foundation; either
|
||||||
|
;;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;;; Lesser General Public License for more details.
|
||||||
|
;;;;
|
||||||
|
;;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (test-lua)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (language tree-il)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (system base compile)
|
||||||
|
#:use-module (test-suite lib)
|
||||||
|
|
||||||
|
#:use-module (language lua parser)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
(with-test-prefix "lua-eval"
|
||||||
|
(define (from-string string)
|
||||||
|
(compile ((make-parser (open-input-string string)))
|
||||||
|
#:from 'lua
|
||||||
|
#:to 'value))
|
||||||
|
(letrec-syntax
|
||||||
|
((test
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ string expect)
|
||||||
|
(pass-if (format "~S => ~S" string expect) (equal? (from-string string) expect)))
|
||||||
|
((_ string)
|
||||||
|
(test string #t)))))
|
||||||
|
|
||||||
|
;; tables
|
||||||
|
(test "a={}; return a[0]" #nil)
|
||||||
|
(test "a={true}; return a[1]" #t)
|
||||||
|
(test "a = { false , true } ; return a[2];" #t)
|
||||||
|
(test "a = { false ; true ; } ; return a[2];" #t)
|
||||||
|
(test "a = { b = true }; return a.b" #t)
|
||||||
|
(test "a = { a = false , false ; b = true , true ; }; return a.b" #t)
|
||||||
|
(test "a = { a = false , false ; b = true , true ; }; return a[2]" #t)
|
||||||
|
|
||||||
|
;; locals
|
||||||
|
(test "local a; a = true; return a")
|
||||||
|
(test "local a = true; return a")
|
||||||
|
(test "local a,b=false,true; return b")
|
||||||
|
(test "local a,b,c=false,true,false; return b")
|
||||||
|
(test "local a,b,c=false,false,true; return c")
|
||||||
|
|
||||||
|
;; local function statements
|
||||||
|
(test "local function identity(x) return x end; return identity(true)")
|
||||||
|
|
||||||
|
;; metatable events
|
||||||
|
(test "table = {} setmetatable(table, { __add = function(a,b) return b end }) return table + 5" 5)
|
||||||
|
(test "table = {} setmetatable(table, { __add = function(a,b) return a end }) return 5 + table" 5)
|
||||||
|
|
||||||
|
;; field functions
|
||||||
|
(test "table = {} function table.identity(x) return x end return table.identity(true)")
|
||||||
|
|
||||||
|
;; repeat
|
||||||
|
(test "i=0; repeat i = i+1 until i == 5; return i" 5)
|
||||||
|
(test "i=5; repeat i = i-1 until i == 0; return i" 0)
|
||||||
|
|
||||||
|
;; length operator
|
||||||
|
(test "return #\"asdf\"" 4)
|
||||||
|
(test "table = {1,2,3,4}; return #table" 4)
|
||||||
|
|
||||||
|
;; _G
|
||||||
|
(test "a = true return _G.a")
|
||||||
|
(test "a = true return _G._G.a")
|
||||||
|
(test "a = true return _G._G._G.a")
|
||||||
|
|
||||||
|
;; concat
|
||||||
|
(test "return \"hello\"..\" world\"" "hello world")
|
||||||
|
|
||||||
|
;; built-in functions
|
||||||
|
(test "assert(true)" #t)
|
||||||
|
(test "print(T)" #nil)
|
||||||
|
(test "print(false or true)" #nil)
|
||||||
|
(test "table = {}; rawset(table, 0, true); return table[0]")
|
||||||
|
(test "table = {}; rawset(table, 0, true); return rawget(table, 0)")
|
||||||
|
|
||||||
|
;; methods
|
||||||
|
(test "table = {} function table:identity() return self end return table.identity(true)")
|
||||||
|
(test "table = {} function table.identity(self,x) return x end return table:identity(true)")
|
||||||
|
|
||||||
|
;; arguments default to nil
|
||||||
|
(test "function test(x) return x end return test()" #nil)
|
||||||
|
|
||||||
|
;; application with freestanding string or table as argument
|
||||||
|
(test "print {x=5}; return true")
|
||||||
|
(test "print \"hello world\"; return true")
|
||||||
|
|
||||||
|
;; variable arguments
|
||||||
|
#;(test "function test(...) print(...) end test(1,2)")
|
||||||
|
|
||||||
|
;; numeric for loop
|
||||||
|
(test "for x = 1,2,1 do print(true) end return true")
|
||||||
|
|
||||||
|
;; list for loop, and ipairs
|
||||||
|
(test "table = {1,2,3} for i,v in ipairs(table) do print(i,v) end return true")
|
||||||
|
))
|
115
test-suite/tests/lua-eval.test
Normal file
115
test-suite/tests/lua-eval.test
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
;;;; lua-eval.test --- basic tests for builtin lua constructs -*- mode: scheme -*-
|
||||||
|
;;;;
|
||||||
|
;;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;;; License as published by the Free Software Foundation; either
|
||||||
|
;;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;;; Lesser General Public License for more details.
|
||||||
|
;;;;
|
||||||
|
;;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (test-lua)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (language tree-il)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (system base compile)
|
||||||
|
#:use-module (test-suite lib)
|
||||||
|
|
||||||
|
#:use-module (language lua parser)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
(with-test-prefix "lua-eval"
|
||||||
|
(define (from-string string)
|
||||||
|
(compile ((make-parser (open-input-string string)))
|
||||||
|
#:from 'lua
|
||||||
|
#:to 'value))
|
||||||
|
(letrec-syntax
|
||||||
|
((test
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ string expect)
|
||||||
|
(pass-if (format "~S => ~S" string expect) (equal? (from-string string) expect)))
|
||||||
|
((_ string)
|
||||||
|
(test string #t)))))
|
||||||
|
|
||||||
|
(test "return true")
|
||||||
|
(test "return false" #f)
|
||||||
|
(test "return nil" #nil)
|
||||||
|
(test "return 12345" 12345)
|
||||||
|
(test "return 12345.6789" 12345.6789)
|
||||||
|
(test "return \"string\"" "string")
|
||||||
|
(test "return (true)")
|
||||||
|
(test "return (false == false)")
|
||||||
|
(test "return;" *unspecified*)
|
||||||
|
(test "return [[string]]" "string")
|
||||||
|
(test "return [=[string]=]" "string")
|
||||||
|
|
||||||
|
;; exercise the operator precedence parser
|
||||||
|
(test "return 2" 2)
|
||||||
|
(test "return 2 + 2" 4)
|
||||||
|
(test "return 1 + 2 * 3" 7)
|
||||||
|
(test "return 1 * 2 + 3" 5)
|
||||||
|
(test "return 1 + 2 ^ 3 * 4 - 5" 28)
|
||||||
|
(test "return 1 ^ 2 - 3 * 4 + 5" -6)
|
||||||
|
(test "return 1 + -6" -5)
|
||||||
|
|
||||||
|
;; logical operators
|
||||||
|
(test "return false or true")
|
||||||
|
(test "return true or false")
|
||||||
|
(test "return false or false or true")
|
||||||
|
(test "return false or nil and true" #nil)
|
||||||
|
(test "return true and true")
|
||||||
|
(test "return true and nil" #nil)
|
||||||
|
(test "return true and false and nil" #f)
|
||||||
|
|
||||||
|
;; conditionals
|
||||||
|
(test "if true then return true end")
|
||||||
|
(test "if false then return false else return true end")
|
||||||
|
(test "if true then return true else return false end")
|
||||||
|
(test "if false then return false elseif true then return true elseif false then return false else return false end")
|
||||||
|
(test "if false then return false elseif false then return false elseif true then return true else return false end")
|
||||||
|
(test "if false then return false elseif false then return false elseif false then return false else return true end")
|
||||||
|
|
||||||
|
;; function expressions
|
||||||
|
(test "(function(x) return x end)(true)")
|
||||||
|
|
||||||
|
;; undefined variables are implicitly nil
|
||||||
|
(test "return undefined == nil")
|
||||||
|
(test "return undefined ~= nil" #f)
|
||||||
|
|
||||||
|
;; assignments
|
||||||
|
(test "variable = true; return variable")
|
||||||
|
(test "a,b = 1,2; return a" 1)
|
||||||
|
(test "a,b=1,2;return b" 2)
|
||||||
|
(test "a,b,c=false,true,false; return b")
|
||||||
|
(test "a,b=1;return b" #nil)
|
||||||
|
|
||||||
|
;; function statements
|
||||||
|
(test "function noargs() return true end noargs()")
|
||||||
|
(test "function identity(x) return x end return identity(21)" 21)
|
||||||
|
(test "function fib(n) if n < 2 then return n else return fib(n-1) + fib(n-2) end end return fib(20)" 6765)
|
||||||
|
(test "function fib(n) if n < 2 then return n else return fib(n-1) + fib(n-2) end end return fib(20)" 6765)
|
||||||
|
|
||||||
|
;; do
|
||||||
|
(test "do return true end")
|
||||||
|
(test "do if false then return false elseif false then return false elseif false then return false else return true end end")
|
||||||
|
|
||||||
|
;; parenthetical expressions
|
||||||
|
(test "return (true);")
|
||||||
|
(test "return (2 + (2))" 4)
|
||||||
|
|
||||||
|
;; while
|
||||||
|
(test "while true do return true end")
|
||||||
|
(test "i=0; while i<5 do i=i+1 end return i" 5)
|
||||||
|
(test "while true do do break end return false end return true")
|
||||||
|
|
||||||
|
))
|
75
test-suite/tests/lua-lexer.test
Normal file
75
test-suite/tests/lua-lexer.test
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
;;;; lua-lexer.test --- lua lexer test suite -*- mode: scheme -*-
|
||||||
|
;;;;
|
||||||
|
;;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;;; License as published by the Free Software Foundation; either
|
||||||
|
;;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;;; Lesser General Public License for more details.
|
||||||
|
;;;;
|
||||||
|
;;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (test-lua-lexer)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (test-suite lib)
|
||||||
|
|
||||||
|
#:use-module (language lua lexer))
|
||||||
|
|
||||||
|
(with-test-prefix "lua-lexer"
|
||||||
|
(define (from-string string)
|
||||||
|
(define-lua-lexer get-source-info lex)
|
||||||
|
(call-with-input-string
|
||||||
|
string
|
||||||
|
(lambda (port)
|
||||||
|
|
||||||
|
(initialize-lua-lexer! port get-source-info lex)
|
||||||
|
(lex))))
|
||||||
|
|
||||||
|
(let-syntax
|
||||||
|
((test
|
||||||
|
(syntax-rules (eof)
|
||||||
|
((_ string expect)
|
||||||
|
(pass-if (format "~S => ~S" string expect) (equal? (from-string string) expect)))
|
||||||
|
((_ (eof string))
|
||||||
|
(pass-if (format "~a => #<eof>" string) (eof-object? (from-string string)))))))
|
||||||
|
|
||||||
|
(test (eof " "))
|
||||||
|
(test (eof "-- comment"))
|
||||||
|
(test (eof "--[[long
|
||||||
|
comment]]"))
|
||||||
|
|
||||||
|
(test "[[long string]]" "long string")
|
||||||
|
(test "[=[[==[longer string]==]]=]" "[==[longer string]==]")
|
||||||
|
|
||||||
|
;; numbers
|
||||||
|
(test "12345" 12345)
|
||||||
|
(test "12345.6789" 12345.6789)
|
||||||
|
(test "12.34e5" 1234000.0)
|
||||||
|
(test ".34e5" 34000.0)
|
||||||
|
|
||||||
|
;; string escapes
|
||||||
|
(test "'\\a\\b\\f\\n\\r\\t\\v'"
|
||||||
|
"\a\b\f\n\r\t\v")
|
||||||
|
(test "'\\''"
|
||||||
|
"'")
|
||||||
|
|
||||||
|
;; operators, keywords, identifiers
|
||||||
|
(test "name" 'name)
|
||||||
|
(test "return" #:return)
|
||||||
|
(test ".." #:concat)
|
||||||
|
(test "..." #:dots)
|
||||||
|
(test ";" #\;)
|
||||||
|
(test "-" #\-)
|
||||||
|
(test "+" #\+)
|
||||||
|
(test "/" #\/)
|
||||||
|
(test "*" #\*)
|
||||||
|
|
||||||
|
))
|
77
test-suite/tests/lua-standard-library.test
Normal file
77
test-suite/tests/lua-standard-library.test
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
;;;; lua standard library test -*- mode: scheme -*-
|
||||||
|
;;;;
|
||||||
|
;;;; Copyright (C) 2010 Free Software Foundation, Inc.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is free software; you can redistribute it and/or
|
||||||
|
;;;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
;;;; License as published by the Free Software Foundation; either
|
||||||
|
;;;; version 3 of the License, or (at your option) any later version.
|
||||||
|
;;;;
|
||||||
|
;;;; This library is distributed in the hope that it will be useful,
|
||||||
|
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
;;;; Lesser General Public License for more details.
|
||||||
|
;;;;
|
||||||
|
;;;; You should have received a copy of the GNU Lesser General Public
|
||||||
|
;;;; License along with this library; if not, write to the Free Software
|
||||||
|
;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
(define-module (test-lua)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
|
#:use-module (language tree-il)
|
||||||
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (srfi srfi-8)
|
||||||
|
#:use-module (system base compile)
|
||||||
|
#:use-module (test-suite lib)
|
||||||
|
|
||||||
|
#:use-module (language lua parser)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
(define (from-string string)
|
||||||
|
(compile ((make-parser (open-input-string string)))
|
||||||
|
#:from 'lua
|
||||||
|
#:to 'value))
|
||||||
|
|
||||||
|
(define-syntax
|
||||||
|
test
|
||||||
|
(syntax-rules ()
|
||||||
|
((_ string expect)
|
||||||
|
(pass-if (format "~S => ~S" string expect) (equal? (from-string string) expect)))
|
||||||
|
((_ string)
|
||||||
|
(test string #t))))
|
||||||
|
|
||||||
|
(with-test-prefix "lua-builtin"
|
||||||
|
(test "assert(true)")
|
||||||
|
(test "rawequal(true,true)")
|
||||||
|
(test "return tonumber('2')" 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
(with-test-prefix "lua-math"
|
||||||
|
(test "require 'math'; return true")
|
||||||
|
(test "return math.abs(-1)" 1)
|
||||||
|
(test "return math.asin(1)" (asin 1))
|
||||||
|
(test "return math.acos(5)" (acos 5))
|
||||||
|
(test "return math.atan(2/1)" (atan (/ 2 1)))
|
||||||
|
(test "return math.atan2(2,1)" (atan (/ 2 1)))
|
||||||
|
(test "return math.ceil(0.5)" (ceiling 0.5))
|
||||||
|
(test "return math.cos(1)" (cos 1))
|
||||||
|
(test "return math.cosh(1)" (cosh 1))
|
||||||
|
(test "return math.floor(0.5)" (floor 0.5))
|
||||||
|
(test "return math.log(10)" (log 10))
|
||||||
|
(test "return math.log10(5)" (log10 5))
|
||||||
|
(test "return math.sqrt(4)" (sqrt 4))
|
||||||
|
(test "return math.sin(5)" (sin 5))
|
||||||
|
(test "return math.sinh(5)" (sinh 5))
|
||||||
|
(test "return math.tan(5)" (tan 5))
|
||||||
|
(test "return math.tanh(5)" (tanh 5))
|
||||||
|
(test "return math.ldexp(4,3)" 32)
|
||||||
|
(test "return math.modf(6,4)" 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
(with-test-prefix "lua-table"
|
||||||
|
(test "require 'table'; return true")
|
||||||
|
(test "t = {}; t[1] = true; t[555] = true; t[1234] = true; return table.maxn(t)" 1234)
|
||||||
|
(test "return table.concat({\"1\", \"2\", \"3\"}, \" \")" "1 2 3")
|
||||||
|
(test "t = {}; t[1] = true; t[2] = false; table.insert(t, 2, true); return t[2]")
|
||||||
|
)
|
Loading…
Add table
Add a link
Reference in a new issue