├── .gitignore
├── LICENSE
├── Makefile.in
├── README.md
├── autogen.sh
├── configure.ac
├── examples
├── maze.jl
├── perf.jl
├── sort.jl
└── test.jl
├── jl.h
├── jl.map
└── src
├── jl-context.c
├── jl-context.h
├── jl-func.c
├── jl-func.h
├── jl-scope.c
├── jl-scope.h
├── jl-value.c
├── jl-value.h
├── jl.c
└── jli.c
/.gitignore:
--------------------------------------------------------------------------------
1 | jli
2 | libjl.a
3 | libjl.so
4 | src/*.o
5 | Makefile
6 | autom4te.cache
7 | config.h
8 | config.h.in
9 | config.h.in~
10 | config.log
11 | config.rpath
12 | config.status
13 | configure
14 | install-sh
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) Joe Wingbermuehle
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | - Redistributions of source code must retain the above copyright notice,
9 | this list of conditions and the following disclaimer.
10 |
11 | - Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/Makefile.in:
--------------------------------------------------------------------------------
1 |
2 | CC = @CC@
3 | CFLAGS = @CFLAGS@
4 | CPPFLAGS = @CPPFLAGS@
5 | LDFLAGS = @LDFLAGS@
6 | BINDIR = $(DESTDIR)@BINDIR@
7 | LIBDIR = $(DESTDIR)@LIBDIR@
8 |
9 | JLOBJS = \
10 | src/jl.o src/jl-context.o src/jl-func.o src/jl-scope.o src/jl-value.o
11 |
12 | REPLOBJS = src/jli.o libjl.a
13 |
14 | .SUFFIXES: .o .h .c
15 |
16 | all: jli libjl.a libjl.so
17 |
18 | install: all
19 | install -d $(BINDIR)
20 | install -d $(LIBDIR)
21 | install jli $(BINDIR)/jl
22 | install libjl.a $(LIBDIR)/libjl.a
23 | install libjl.so $(LIBDIR)/libjl.so
24 |
25 | install-strip: install
26 | strip $(BINDIR)/jli
27 |
28 | jli: $(REPLOBJS)
29 | $(CC) $(LDFLAGS) $(REPLOBJS) -o jli
30 |
31 | libjl.so: $(JLOBJS)
32 | $(CC) $(LDFLAGS) -shared $(JLOBJS) -o libjl.so
33 |
34 | libjl.a: $(JLOBJS)
35 | ar r libjl.a $(JLOBJS)
36 |
37 | .c.o: $*.c *.h
38 | $(CC) $(CFLAGS) -c $*.c -o $*.o
39 |
40 | clean:
41 | rm -f jli libjl.a libjl.so src/*.o
42 |
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | JL
3 | ==============================================================================
4 | This is a small, embeddable LISP-like language. The intended use is for
5 | configuration files where it is desirable to be able to have complex
6 | configurations (JWM, for example).
7 |
8 | This is still a work in progress: there are still more functions to
9 | be implemented and the functionality of existing functions may change.
10 |
11 | Syntax
12 | ------------------------------------------------------------------------------
13 | Like other LISP languages, JL uses s-expressions. For example:
14 |
15 | (list 1 2 3)
16 |
17 | calls the "list" function, passing 1, 2, and 3 as arguments.
18 |
19 | Data Types
20 | ------------------------------------------------------------------------------
21 | There are 6 data types:
22 |
23 | 1. Numbers (floating point numbers)
24 | 2. Strings
25 | 3. Variables
26 | 4. Lambdas (functions defined within the language)
27 | 5. Lists
28 | 6. Special functions
29 |
30 | For comparisons, 0 and nil (the empty list) are considered false and all
31 | other values are considered true.
32 |
33 | Functions
34 | ------------------------------------------------------------------------------
35 | The following built-in functions are available:
36 |
37 | - < Test if less than
38 | - > Test if greater than
39 | - <= Test if less than or equal to
40 | - >= Test if greater than or equal to
41 | - = Test if equal
42 | - != Test if not equal
43 | - + Return the sum of a list
44 | - - Subtract
45 | - * Return the produce of a list
46 | - / Divide
47 | - % Modulus
48 | - and Logical AND.
49 | - concat Concatenate strings.
50 | - cons Prepend an item to a list.
51 | - begin Execute a sequence of functions, return the value of the last.
52 | - define Insert a binding into the current namespace.
53 | - head Return the first element of a list
54 | - if Test a condition and evaluate and return the second argument
55 | if true, otherwise evaluate and return the third argument.
56 | - lambda Declare a function.
57 | - list Create a list
58 | - list? Determine if a value is a list.
59 | - not Logical NOT.
60 | - null? Determine if a value is nil.
61 | - number? Determine if a value is a number.
62 | - or Logical OR.
63 | - rest Return all but the first element of a list
64 | - string? Determine if a value is a string.
65 | - substr Return a substring of a string.
66 |
67 | Examples
68 | ------------------------------------------------------------------------------
69 | Here are some example programs. See the "examples" directory for more.
70 |
71 | Return the factorial of a number:
72 |
73 | (define fact (lambda (n)
74 | (if n
75 | (\* (fact (- n 1)) n)
76 | 1)))
77 | (fact 5)
78 |
79 |
80 | Find the nth item of a list:
81 |
82 | (define nth (lambda (n lst)
83 | (if (<= n 1)
84 | (head lst)
85 | (nth (- n 1) (rest lst)))))
86 | (nth 2 (list 1 2 3))
87 |
88 |
89 | Find nth Fibonacci number:
90 |
91 | (define fib (lambda (n)
92 | (if (> n 1)
93 | (+ (fib (- n 1)) (fib (- n 2)))
94 | 1)))
95 | (fib 10)
96 |
97 |
98 | The map function:
99 |
100 | (define map (lambda (f lst)
101 | (if lst
102 | (cons (f (head lst)) (map f (rest lst)))
103 | (list))))
104 | (map (lambda (x) (+ x 1)) (list 1 2 3 4))
105 |
106 |
107 | The foldl function:
108 |
109 | (define foldl (lambda (f i lst)
110 | (if lst
111 | (foldl f (f i (head lst)) (rest lst))
112 | i)))
113 | (foldl (lambda (a b) (+ a b)) 0 (list 1 2 3 4))
114 |
115 |
116 | The reverse function implemented in terms of foldl:
117 |
118 | (define reverse (lambda (lst) (foldl (lambda (a b) (cons b a)) (list) lst)))
119 | (reverse (list 1 2 3 4))
120 |
121 |
122 | License
123 | ------------------------------------------------------------------------------
124 | JL uses the BSD 2-clause license. See LICENSE for more information.
125 |
126 |
--------------------------------------------------------------------------------
/autogen.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | automake -a
3 | autoreconf --install --force
4 | touch config.rpath
5 |
--------------------------------------------------------------------------------
/configure.ac:
--------------------------------------------------------------------------------
1 | AC_INIT(jl, 0.0.1, joewing@ojewing.net)
2 | AC_PREREQ(2.57)
3 | AC_CONFIG_SRCDIR([src])
4 | AC_CONFIG_HEADER([config.h])
5 | AC_LANG(C)
6 |
7 | AC_PROG_CC
8 | AC_PROG_CPP
9 | AC_PROG_INSTALL
10 |
11 | CFLAGS="$CFLAGS -I. -fpic -fvisibility=hidden"
12 |
13 | AC_ARG_ENABLE(debug,
14 | AC_HELP_STRING([--enable-debug], [create a debug build]) )
15 | if test "$enable_debug" = "yes"; then
16 | CFLAGS="$CFLAGS -g -Wall -Werror"
17 | LDFLAGS="$LDFLAGS -g"
18 | else
19 | enable_debug="no"
20 | fi
21 |
22 | PACKAGE=jl
23 |
24 | AC_SUBST(CFLAGS)
25 | AC_SUBST(LDFLAGS)
26 | AC_SUBST(VERSION, "$PACKAGE_VERSION")
27 | AC_SUBST(INSTVERSION, `echo $PACKAGE_VERSION | tr -d .`)
28 | AC_SUBST(BINDIR, "$BINDIR")
29 | AC_SUBST(MANDIR, "$MANDIR")
30 | AC_SUBST(DATADIR, "$DATADIR")
31 | AC_SUBST(DATE, `date -u -d "@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d`)
32 | AC_SUBST(SYSCONF, "$SYSCONF")
33 | AC_SUBST(PACKAGE, "$PACKAGE")
34 |
35 |
36 | AC_OUTPUT(
37 | Makefile
38 | )
39 |
--------------------------------------------------------------------------------
/examples/maze.jl:
--------------------------------------------------------------------------------
1 | ; Maze generate in JL
2 | ; Joe Wingbermuehle
3 |
4 | (define width 39) ; Width of the maze (must be odd)
5 | (define height 23) ; Height of the maze (must be odd)
6 | (define seed 17) ; Random number seed.
7 |
8 | ; Generate a random number.
9 | (define get-rand (lambda (s) (mod (+ (* 19 s) 1) 16383)))
10 |
11 | ; Set an element in a list.
12 | (define set-element (lambda (lst n v)
13 | (if n
14 | (cons (head lst) (set-element (rest lst) (- n 1) v))
15 | (cons v (rest lst)))))
16 |
17 | ; Get a particular list element.
18 | (define get-element (lambda (lst n)
19 | (if n
20 | (get-element (rest lst) (- n 1))
21 | (head lst))))
22 |
23 | ; Initialize the maze matrix.
24 | (define init-maze (lambda (x y)
25 | (if (= y 1)
26 | (if (= x 1) (list 0) (cons 0 (init-maze (- x 1) y)))
27 | (if (= y height)
28 | (if (= x 1)
29 | (cons 0 (init-maze width (- y 1)))
30 | (cons 0 (init-maze (- x 1) y)))
31 | (if (= x 1)
32 | (cons 0 (init-maze width (- y 1)))
33 | (if (= x width)
34 | (cons 0 (init-maze (- x 1) y))
35 | (cons 1 (init-maze (- x 1) y))))))))
36 |
37 | (define update-x (lambda (x d) (+ x (get-element (list 1 -1 0 0) (mod d 4)))))
38 |
39 | (define update-y (lambda (y d) (+ y (get-element (list 0 0 1 -1) (mod d 4)))))
40 |
41 | ; Carve a maze.
42 | (define carve-maze (lambda (maze rand x y c)
43 | (define maze (set-element maze (+ (* y width) x) 0))
44 | (define x1 (update-x x rand))
45 | (define y1 (update-y y rand))
46 | (define x2 (update-x x1 rand))
47 | (define y2 (update-y y1 rand))
48 | (if (and (> x2 0) (< x2 width) (> y2 0) (< y2 height)
49 | (get-element maze (+ (* y1 width) x1))
50 | (get-element maze (+ (* y2 width) x2)))
51 | (begin
52 | (define maze (set-element maze (+ (* y1 width) x1) 0))
53 | (define maze (carve-maze maze (get-rand rand) x2 y2 0))
54 | (if (< c 4) (carve-maze maze (+ rand 1) x y (+ c 1)) maze))
55 | (if (< c 4) (carve-maze maze (+ rand 1) x y (+ c 1)) maze))))
56 |
57 | ; Initialize and carve a maze.
58 | (define generate-maze (lambda ()
59 | (define init (init-maze width height))
60 | (define carved (carve-maze init seed 2 2 0))
61 | (define temp (set-element carved (+ (* 1 width) 2) 0))
62 | (set-element temp (+ (* (- height 2) width) (- width 3)) 0)))
63 |
64 | ; Show a maze.
65 | (define show-maze (lambda (maze x y)
66 | (define element (get-element maze (+ (* y width) x)))
67 | (if (= element 1) (print "[]") (print " "))
68 | (if (= (+ x 1) width)
69 | (begin
70 | (print "\n")
71 | (if (< (+ y 1) height) (show-maze maze 0 (+ y 1))))
72 | (show-maze maze (+ x 1) y))))
73 |
74 | (show-maze (generate-maze) 0 0)
75 |
76 |
--------------------------------------------------------------------------------
/examples/perf.jl:
--------------------------------------------------------------------------------
1 | ;; Performance test for JL
2 |
3 | (define perf1 (lambda (n) (if n (+ n (perf1 (- n 1))) 0)))
4 | (define perf (lambda (n) (if n (+ (perf1 n) (perf (- n 1))) 0)))
5 |
6 | (perf 2048)
7 |
8 |
--------------------------------------------------------------------------------
/examples/sort.jl:
--------------------------------------------------------------------------------
1 | ;; QuickSort in JL.
2 |
3 | (define filter (lambda (f lst)
4 | (if lst
5 | (if (f (head lst))
6 | (cons (head lst) (filter f (rest lst)))
7 | (filter f (rest lst)))
8 | nil)))
9 |
10 | (define combine (lambda (as bs)
11 | (if as
12 | (cons (head as) (combine (rest as) bs))
13 | bs)))
14 |
15 | (define qsort (lambda (lst)
16 | (if lst
17 | (begin
18 | (define pivot (head lst))
19 | (define less (filter (lambda (a) (< a pivot)) (rest lst)))
20 | (define greater (filter (lambda (a) (>= a pivot)) (rest lst)))
21 | (combine (qsort less) (cons pivot (qsort greater))))
22 | nil)))
23 |
24 | (print (qsort (list 7 3 2 6 9 1 8 4 5)) "\n")
25 |
26 |
--------------------------------------------------------------------------------
/examples/test.jl:
--------------------------------------------------------------------------------
1 | ;;; Test suite for JL
2 |
3 | ; Assert that an expression is true. Prints a dot if true, or FAIL if false.
4 | (define assert (lambda (tst) (if tst (print ".") (print "\nFAIL\n"))))
5 |
6 | (define fib (lambda (n)
7 | (if (> n 1) ; Comment in the middle of a line
8 | (+ (fib (- n 1)) (fib (- n 2)))
9 | 1)))
10 |
11 | (assert (= (fib 4) 5))
12 | (assert (= (fib 0) 1))
13 |
14 | (define fact (lambda (n)
15 | (if (> n 0) (* (fact (- n 1)) n) 1)))
16 |
17 | (assert (= (fact 5) 120))
18 |
19 | (define length (lambda (lst)
20 | (if (null? lst) 0 (+ 1 (length (rest lst))))))
21 |
22 | (assert (= (length (list 1 2 3 4)) 4))
23 | (assert (= (length (list)) 0))
24 |
25 | (define nth (lambda (n lst)
26 | (if (<= n 1) (head lst)
27 | (nth (- n 1) (rest lst)))))
28 |
29 | (assert (= (nth 2 (list 5 4 3 2 1)) 4))
30 | (assert (= (nth 1 (list 5 4 3 2 1)) 5))
31 |
32 | (define map (lambda (f lst)
33 | (if (null? lst) nil (cons (f (head lst)) (map f (rest lst))))))
34 |
35 | (define foldl (lambda (f i lst)
36 | (if lst (foldl f (f i (head lst)) (rest lst)) i)))
37 |
38 | (assert (= (foldl (lambda (a b) (+ a b)) 1 (list 2 3 4)) 10))
39 |
40 | (assert (= (foldl (lambda (a b) (+ a b)) 0
41 | (map (lambda (x) (+ x 1)) (list 0 1 2 3))) 10))
42 |
43 | (define reverse (lambda (lst) (foldl (lambda (a b) (cons b a)) (list) lst)))
44 |
45 | (define make-increment (lambda (i) (lambda (x) (+ x i))))
46 | (define add-five (make-increment 5))
47 | (assert (= (add-five 2) 7))
48 |
49 | (define add (lambda (x y) (+ x y)))
50 | (define do-define (lambda (x)
51 | (define add (lambda (y) (- x y)))
52 | (add 1)))
53 | (assert (= (do-define 3) 2))
54 | (assert (= (add 1 2) 3))
55 |
56 | (define x 1)
57 | (define x 2)
58 | (assert (= x 2))
59 |
60 | (assert (= (begin 1 2) 2))
61 |
62 | (define y 1)
63 | (assert (= y 1))
64 | (begin
65 | (assert (= y 1))
66 | (define y 2)
67 | (assert (= y 2)))
68 | (assert (= y 1))
69 |
70 | (assert (not (and 0 (assert 0))))
71 | (assert (= 1 (or 1)))
72 | (assert (= 1 (not 0)))
73 |
74 | (define strlen (lambda (str)
75 | (define helper (lambda (i) (if (substr str i 1) (helper (+ i 1)) i)))
76 | (helper 0)))
77 |
78 | (assert (= (strlen "asdf") 4))
79 |
80 | (assert (= 1 (number? 5)))
81 | (assert (= nil (number? nil)))
82 | (assert (= nil (number? "test")))
83 | (assert (= nil (number? (list 1 2 3))))
84 | (assert (= 1 (number? (head (list 1 2 3)))))
85 |
86 | (assert (= 1 (string? "asfd")))
87 | (assert (= nil (string? nil)))
88 | (assert (= nil (string? 5)))
89 | (assert (= nil (string? (list 1 2 3))))
90 | (assert (= 1 (string? (head (list "asdf" 2 3)))))
91 |
92 | (assert (= 1 (list? (list 1 2))))
93 | (assert (= nil (list? nil)))
94 | (assert (= nil (list? 5)))
95 | (assert (= nil (list? "test")))
96 | (assert (= 1 (list? (head (list (list 1 2) 2 3)))))
97 |
98 | (assert (< "a" "b"))
99 | (assert (= "asdf" "asdf"))
100 | (assert (<= "a" "b"))
101 | (assert (<= "a" "a"))
102 | (assert (!= "ab" "ac"))
103 | (assert (> "b" "a"))
104 | (assert (>= "b" "a"))
105 | (assert (>= "a" "a"))
106 |
107 | (assert (= ((lambda (a b) (+ a b 1)) 2 3) 6))
108 |
109 | ; Test concat and whitespace before closing ')'.
110 | (define repeat
111 | (lambda (str i)
112 | (if (= i 1)
113 | str
114 | (if (> i 0)
115 | (concat (repeat str (- i 1)) "," str)
116 | ""
117 | )
118 | )
119 | )
120 | )
121 | (assert (= "0123456789,0123456789,0123456789" (repeat "0123456789" 3)))
122 |
123 | (print "\ndone\n")
124 |
125 |
--------------------------------------------------------------------------------
/jl.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl.h
3 | * @author Joe Wingbermuehle
4 | *
5 | * Public interface to the JL interpreter.
6 | *
7 | */
8 |
9 | #ifndef JL_H
10 | #define JL_H
11 |
12 | #ifdef __cplusplus
13 | extern "C" {
14 | #endif
15 |
16 | #define JL_VERSION_MAJOR 0
17 | #define JL_VERSION_MINOR 1
18 |
19 | #if defined _WIN32 || defined __CYGWIN__
20 | # define JLEXPORT __declspec(dllexport)
21 | #else
22 | # define JLEXPORT __attribute__((visibility("default")))
23 | #endif
24 |
25 | struct JLValue;
26 | struct JLContext;
27 |
28 | /** The type of special functions.
29 | * @param context The JL context.
30 | * @param args A list of arguments to the function, including its name.
31 | * @param extra Extra parameter from JLDefineSpecial.
32 | * @return The result, which should be retained (it will be freed if
33 | * not needed).
34 | */
35 | typedef struct JLValue *(*JLFunction)(struct JLContext *context,
36 | struct JLValue *args,
37 | void *extra);
38 |
39 | /** Create a context for running JL programs.
40 | * @return The context.
41 | */
42 | JLEXPORT
43 | struct JLContext *JLCreateContext();
44 |
45 | /** Destroy a JL context.
46 | * @param context The context to be destroyed.
47 | */
48 | JLEXPORT
49 | void JLDestroyContext(struct JLContext *context);
50 |
51 | /** Create and enter a new lexical scope. */
52 | JLEXPORT
53 | void JLEnterScope(struct JLContext *context);
54 |
55 | /** Leave and destroy the current lexical scope. */
56 | JLEXPORT
57 | void JLLeaveScope(struct JLContext *context);
58 |
59 | /** Increase the reference count of a value.
60 | * @param context The context containing the value.
61 | * @param value The value (can be NULL).
62 | */
63 | JLEXPORT
64 | void JLRetain(struct JLContext *context, struct JLValue *value);
65 |
66 | /** Decrease the reference count of a value.
67 | * This will destroy the value if the reference count reaches zero.
68 | * @param context The context.
69 | * @param value The value to be released (can be NULL).
70 | */
71 | JLEXPORT
72 | void JLRelease(struct JLContext *context, struct JLValue *value);
73 |
74 | /** Define a value.
75 | * This will add a binding to the current scope.
76 | * @param context The context in which to define the value.
77 | * @param name The name of the binding.
78 | * @param value The value to insert.
79 | */
80 | JLEXPORT
81 | void JLDefineValue(struct JLContext *context,
82 | const char *name,
83 | struct JLValue *value);
84 |
85 | /** Define a special function.
86 | * A special function is simply a function that is implemented outside
87 | * of the JL environment.
88 | * This will add the special function to the current scope.
89 | * @param context The context in which to define the function.
90 | * @param name The name of the function.
91 | * @param func The function code.
92 | * @param extra Extra parameter to pass to func.
93 | */
94 | JLEXPORT
95 | void JLDefineSpecial(struct JLContext *context,
96 | const char *name,
97 | JLFunction func,
98 | void *extra);
99 |
100 | /** Define a number.
101 | * This will add a number to the current scope.
102 | * @param context The context in which to define the number.
103 | * @param name The name of the binding (NULL for no name).
104 | * @param value The value to define.
105 | * @return The value. This value must be released if not used.
106 | */
107 | JLEXPORT
108 | struct JLValue *JLDefineNumber(struct JLContext *context,
109 | const char *name,
110 | double value);
111 |
112 | /** Parse an expression.
113 | * Note that only a single expression is parsed.
114 | * @param context The context.
115 | * @param line The expression to be parsed.
116 | * @return The expression. This value must be released if not used.
117 | */
118 | JLEXPORT
119 | struct JLValue *JLParse(struct JLContext *context, const char **line);
120 |
121 | /** Evaluate an expression.
122 | * @param context The context.
123 | * @param value The expression to evaluate.
124 | * @return The result. This value must be released if not used.
125 | */
126 | JLEXPORT
127 | struct JLValue *JLEvaluate(struct JLContext *context, struct JLValue *value);
128 |
129 | /** Determine if a value is a number.
130 | * @param value The value to check (NULL is allowed).
131 | * @return 1 if a number, 0 otherwise.
132 | */
133 | JLEXPORT
134 | char JLIsNumber(struct JLValue *value);
135 |
136 | /** Get the number representation of a value.
137 | * @param value The value (must be a non-NULL number value).
138 | * @return The numeric value.
139 | */
140 | JLEXPORT
141 | double JLGetNumber(struct JLValue *value);
142 |
143 | /** Determine if a value is a string.
144 | * @param value The value to check (NULL is allowed).
145 | * @return 1 if a string, 0 otherwise.
146 | */
147 | JLEXPORT
148 | char JLIsString(struct JLValue *value);
149 |
150 | /** Get the string representation of a value.
151 | * @param value The value (must be a non-NULL string value).
152 | * @return A NULL-terminated string.
153 | */
154 | JLEXPORT
155 | const char *JLGetString(struct JLValue *value);
156 |
157 | /** Determine if a value is a list.
158 | * @param value The value to check.
159 | * @return 1 if a list, 0 otherwise.
160 | */
161 | JLEXPORT
162 | char JLIsList(struct JLValue *value);
163 |
164 | /** Get the first item of a list.
165 | * @param value The list (must be a non-NULL list value).
166 | * @return The first item in the list (possibly NULL).
167 | */
168 | JLEXPORT
169 | struct JLValue *JLGetHead(struct JLValue *value);
170 |
171 | /** Get the next item in a list.
172 | * @param value The previous value in the list (must be non-NULL).
173 | * @return The next item in the list (possibly NULL).
174 | */
175 | JLEXPORT
176 | struct JLValue *JLGetNext(struct JLValue *value);
177 |
178 | /** Display a value.
179 | * @param context The context.
180 | * @param value The value to display.
181 | */
182 | JLEXPORT
183 | void JLPrint(const struct JLContext *context, const struct JLValue *value);
184 |
185 | #ifdef __cplusplus
186 | } /* extern "C" */
187 | #endif
188 |
189 | #endif /* JL_H */
190 |
--------------------------------------------------------------------------------
/jl.map:
--------------------------------------------------------------------------------
1 |
2 | JL_1.0 {
3 | global: JLCreateContext;
4 | JLDestroyContext;
5 | JLEnterScope;
6 | JLLeaveScope;
7 | JLRetain;
8 | JLRelease;
9 | JLDefineValue;
10 | JLDefineSpecial;
11 | JLDefineNumber;
12 | JLParse;
13 | JLEvaluate;
14 | JLIsNumber;
15 | JLGetNumber;
16 | JLIsString;
17 | JLGetString;
18 | JLIsList;
19 | JLGetHead;
20 | JLGetNext;
21 | JLPrint;
22 | local: *;
23 | };
24 |
--------------------------------------------------------------------------------
/src/jl-context.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-context.c
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #include "jl.h"
7 | #include "jl-context.h"
8 | #include "jl-scope.h"
9 | #include "jl-value.h"
10 |
11 | #include
12 | #include
13 | #include
14 |
15 | #define BLOCK_SIZE 8192
16 |
17 | typedef struct FreeNode {
18 | union {
19 | BindingNode binding;
20 | ScopeNode scope;
21 | JLValue value;
22 | struct FreeNode *next;
23 | };
24 | } FreeNode;
25 |
26 | typedef struct BlockNode {
27 | FreeNode nodes[BLOCK_SIZE];
28 | struct BlockNode *next;
29 | } BlockNode;
30 |
31 | void *GetFree(JLContext *context)
32 | {
33 | FreeNode *node;
34 | if(context->freelist == NULL) {
35 | BlockNode *block = (BlockNode*)malloc(sizeof(BlockNode));
36 | size_t i;
37 | block->next = context->blocks;
38 | context->blocks = block;
39 | for(i = 1; i < BLOCK_SIZE; i++) {
40 | block->nodes[i].next = context->freelist;
41 | context->freelist = &block->nodes[i];
42 | }
43 | node = &block->nodes[0];
44 | } else {
45 | node = context->freelist;
46 | context->freelist = node->next;
47 | }
48 | return node;
49 | }
50 |
51 | void PutFree(JLContext *context, void *value)
52 | {
53 | FreeNode *temp = (FreeNode*)value;
54 | temp->next = context->freelist;
55 | context->freelist = temp;
56 | }
57 |
58 | void FreeContext(JLContext *context)
59 | {
60 | while(context->blocks) {
61 | BlockNode *next = context->blocks->next;
62 | free(context->blocks);
63 | context->blocks = next;
64 | }
65 | free(context);
66 | }
67 |
68 | void Error(JLContext *context, const char *msg, ...)
69 | {
70 | va_list ap;
71 | va_start(ap, msg);
72 | context->error = 1;
73 | printf("ERROR[%d]: ", context->line);
74 | vprintf(msg, ap);
75 | printf("\n");
76 | va_end(ap);
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/src/jl-context.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-context.h
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #ifndef JL_CONTEXT_H
7 | #define JL_CONTEXT_H
8 |
9 | struct ScopeNode;
10 | struct FreeNode;
11 | struct BlockNode;
12 |
13 | typedef struct JLContext {
14 | struct ScopeNode *scope;
15 | struct FreeNode *freelist;
16 | struct BlockNode *blocks;
17 | unsigned int line;
18 | unsigned int levels;
19 | unsigned int max_levels;
20 | char error;
21 | } JLContext;
22 |
23 | void *GetFree(JLContext *context);
24 |
25 | void PutFree(JLContext *context, void *value);
26 |
27 | void FreeContext(JLContext *context);
28 |
29 | void Error(JLContext *context, const char *msg, ...);
30 |
31 | #endif /* JL_CONTEXT_H */
32 |
--------------------------------------------------------------------------------
/src/jl-func.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-func.h
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #include "jl.h"
7 | #include "jl-func.h"
8 | #include "jl-value.h"
9 | #include "jl-context.h"
10 | #include "jl-scope.h"
11 |
12 | #include
13 | #include
14 | #include
15 |
16 | typedef struct InternalFunctionNode {
17 | const char *name;
18 | JLFunction function;
19 | } InternalFunctionNode;
20 |
21 | static char CheckCondition(JLContext *context, JLValue *value);
22 | static void InvalidArgumentError(JLContext *context, JLValue *args);
23 | static void TooManyArgumentsError(JLContext *context, JLValue *args);
24 | static void TooFewArgumentsError(JLContext *context, JLValue *args);
25 |
26 | static JLValue *CompareFunc(JLContext *context, JLValue *args, void *extra);
27 | static JLValue *AddFunc(JLContext *context, JLValue *args, void *extra);
28 | static JLValue *SubFunc(JLContext *context, JLValue *args, void *extra);
29 | static JLValue *MulFunc(JLContext *context, JLValue *args, void *extra);
30 | static JLValue *DivFunc(JLContext *context, JLValue *args, void *extra);
31 | static JLValue *ModFunc(JLContext *context, JLValue *args, void *extra);
32 | static JLValue *AndFunc(JLContext *context, JLValue *args, void *extra);
33 | static JLValue *OrFunc(JLContext *context, JLValue *args, void *extra);
34 | static JLValue *NotFunc(JLContext *context, JLValue *args, void *extra);
35 | static JLValue *BeginFunc(JLContext *context, JLValue *args, void *extra);
36 | static JLValue *ConsFunc(JLContext *context, JLValue *args, void *extra);
37 | static JLValue *DefineFunc(JLContext *context, JLValue *args, void *extra);
38 | static JLValue *HeadFunc(JLContext *context, JLValue *args, void *extra);
39 | static JLValue *IfFunc(JLContext *context, JLValue *args, void *extra);
40 | static JLValue *LambdaFunc(JLContext *context, JLValue *args, void *extra);
41 | static JLValue *ListFunc(JLContext *context, JLValue *args, void *extra);
42 | static JLValue *RestFunc(JLContext *context, JLValue *args, void *extra);
43 | static JLValue *SubstrFunc(JLContext *context, JLValue *args, void *extra);
44 | static JLValue *ConcatFunc(JLContext *context, JLValue *args, void *extra);
45 | static JLValue *IsNumberFunc(JLContext *context, JLValue *args, void *extra);
46 | static JLValue *IsStringFunc(JLContext *context, JLValue *args, void *extra);
47 | static JLValue *IsListFunc(JLContext *context, JLValue *args, void *extra);
48 | static JLValue *IsNullFunc(JLContext *context, JLValue *args, void *extra);
49 |
50 | static InternalFunctionNode INTERNAL_FUNCTIONS[] = {
51 | { "=", CompareFunc },
52 | { "!=", CompareFunc },
53 | { ">", CompareFunc },
54 | { ">=", CompareFunc },
55 | { "<", CompareFunc },
56 | { "<=", CompareFunc },
57 | { "+", AddFunc },
58 | { "-", SubFunc },
59 | { "*", MulFunc },
60 | { "/", DivFunc },
61 | { "mod", ModFunc },
62 | { "and", AndFunc },
63 | { "or", OrFunc },
64 | { "not", NotFunc },
65 | { "begin", BeginFunc },
66 | { "cons", ConsFunc },
67 | { "define", DefineFunc },
68 | { "head", HeadFunc },
69 | { "if", IfFunc },
70 | { "lambda", LambdaFunc },
71 | { "list", ListFunc },
72 | { "rest", RestFunc },
73 | { "substr", SubstrFunc },
74 | { "concat", ConcatFunc },
75 | { "number?", IsNumberFunc },
76 | { "string?", IsStringFunc },
77 | { "list?", IsListFunc },
78 | { "null?", IsNullFunc }
79 | };
80 | static size_t INTERNAL_FUNCTION_COUNT = sizeof(INTERNAL_FUNCTIONS)
81 | / sizeof(InternalFunctionNode);
82 |
83 | char CheckCondition(JLContext *context, JLValue *value)
84 | {
85 | JLValue *cond = JLEvaluate(context, value);
86 | char rc = 0;
87 | if(cond) {
88 | switch(cond->tag) {
89 | case JLVALUE_NUMBER:
90 | rc = cond->value.number != 0.0;
91 | break;
92 | case JLVALUE_LIST:
93 | rc = cond->value.lst != NULL;
94 | break;
95 | default:
96 | rc = 1;
97 | break;
98 | }
99 | JLRelease(context, cond);
100 | }
101 | return rc;
102 | }
103 |
104 | void InvalidArgumentError(JLContext *context, JLValue *args)
105 | {
106 | Error(context, "invalid argument to %s", args->value.str);
107 | }
108 |
109 | void TooManyArgumentsError(JLContext *context, JLValue *args)
110 | {
111 | Error(context, "too many arguments to %s", args->value.str);
112 | }
113 |
114 | void TooFewArgumentsError(JLContext *context, JLValue *args)
115 | {
116 | Error(context, "too few arguments to %s", args->value.str);
117 | }
118 |
119 | JLValue *CompareFunc(JLContext *context, JLValue *args, void *extra)
120 | {
121 | const char *op = args->value.str;
122 | JLValue *va = NULL;
123 | JLValue *vb = NULL;
124 | JLValue *result = NULL;
125 | char cond = 0;
126 | if(args->next == NULL || args->next->next == NULL) {
127 | TooFewArgumentsError(context, args);
128 | return NULL;
129 | }
130 | if(args->next->next->next) {
131 | TooManyArgumentsError(context, args);
132 | return NULL;
133 | }
134 |
135 | va = JLEvaluate(context, args->next);
136 | vb = JLEvaluate(context, args->next->next);
137 | if(va == NULL || vb == NULL || va->tag != vb->tag) {
138 |
139 | if(op[0] == '=') {
140 | cond = va == vb;
141 | } else if(op[0] == '!') {
142 | cond = va != vb;
143 | } else {
144 | InvalidArgumentError(context, args);
145 | }
146 |
147 | } else {
148 |
149 | /* Here we know that va and vb are not nil and are of the same type. */
150 | double diff = 0.0;
151 | if(va->tag == JLVALUE_NUMBER) {
152 | diff = va->value.number - vb->value.number;
153 | } else if(va->tag == JLVALUE_STRING) {
154 | diff = strcmp(va->value.str, vb->value.str);
155 | } else {
156 | InvalidArgumentError(context, args);
157 | }
158 |
159 | if(op[0] == '=') {
160 | cond = diff == 0.0;
161 | } else if(op[0] == '!') {
162 | cond = diff != 0.0;
163 | } else if(op[0] == '<' && op[1] == 0) {
164 | cond = diff < 0.0;
165 | } else if(op[0] == '<' && op[1] == '=') {
166 | cond = diff <= 0.0;
167 | } else if(op[0] == '>' && op[1] == 0) {
168 | cond = diff > 0.0;
169 | } else if(op[0] == '>' && op[1] == '=') {
170 | cond = diff >= 0.0;
171 | }
172 |
173 | }
174 |
175 | if(cond) {
176 | result = JLDefineNumber(context, NULL, 1.0);
177 | }
178 |
179 | JLRelease(context, va);
180 | JLRelease(context, vb);
181 | return result;
182 | }
183 |
184 | JLValue *AddFunc(JLContext *context, JLValue *args, void *extra)
185 | {
186 | JLValue *vp;
187 | double sum = 0.0;
188 | for(vp = args->next; vp; vp = vp->next) {
189 | JLValue *arg = JLEvaluate(context, vp);
190 | if(arg == NULL || arg->tag != JLVALUE_NUMBER) {
191 | InvalidArgumentError(context, args);
192 | JLRelease(context, arg);
193 | return NULL;
194 | }
195 | sum += arg->value.number;
196 | JLRelease(context, arg);
197 | }
198 | return JLDefineNumber(context, NULL, sum);
199 | }
200 |
201 | JLValue *SubFunc(JLContext *context, JLValue *args, void *extra)
202 | {
203 | JLValue *vp = args->next;
204 | JLValue *arg = NULL;
205 | double total = 0.0;
206 |
207 | arg = JLEvaluate(context, vp);
208 | if(arg == NULL || arg->tag != JLVALUE_NUMBER) {
209 | InvalidArgumentError(context, args);
210 | JLRelease(context, arg);
211 | return NULL;
212 | }
213 | total = arg->value.number;
214 | JLRelease(context, arg);
215 |
216 | for(vp = vp->next; vp; vp = vp->next) {
217 | arg = JLEvaluate(context, vp);
218 | if(arg == NULL || arg->tag != JLVALUE_NUMBER) {
219 | InvalidArgumentError(context, args);
220 | JLRelease(context, arg);
221 | return NULL;
222 | }
223 | total -= arg->value.number;
224 | JLRelease(context, arg);
225 | }
226 |
227 | return JLDefineNumber(context, NULL, total);
228 |
229 | }
230 |
231 | JLValue *MulFunc(JLContext *context, JLValue *args, void *extra)
232 | {
233 | JLValue *vp;
234 | double product = 1.0;
235 | for(vp = args->next; vp; vp = vp->next) {
236 | JLValue *arg = JLEvaluate(context, vp);
237 | if(arg == NULL || arg->tag != JLVALUE_NUMBER) {
238 | InvalidArgumentError(context, args);
239 | JLRelease(context, arg);
240 | return NULL;
241 | }
242 | product *= arg->value.number;
243 | JLRelease(context, arg);
244 | }
245 | return JLDefineNumber(context, NULL, product);
246 | }
247 |
248 | JLValue *DivFunc(JLContext *context, JLValue *args, void *extra)
249 | {
250 | JLValue *va = NULL;
251 | JLValue *vb = NULL;
252 | JLValue *result = NULL;
253 |
254 | va = JLEvaluate(context, args->next);
255 | if(va == NULL || va->tag != JLVALUE_NUMBER) {
256 | InvalidArgumentError(context, args);
257 | goto div_done;
258 | }
259 | vb = JLEvaluate(context, args->next->next);
260 | if(vb == NULL || vb->tag != JLVALUE_NUMBER) {
261 | InvalidArgumentError(context, args);
262 | goto div_done;
263 | }
264 | if(args->next->next->next) {
265 | TooManyArgumentsError(context, args);
266 | goto div_done;
267 | }
268 |
269 | result = JLDefineNumber(context, NULL, va->value.number / vb->value.number);
270 |
271 | div_done:
272 |
273 | JLRelease(context, va);
274 | JLRelease(context, vb);
275 | return result;
276 |
277 | }
278 |
279 | JLValue *ModFunc(JLContext *context, JLValue *args, void *extra)
280 | {
281 |
282 | JLValue *va = NULL;
283 | JLValue *vb = NULL;
284 | JLValue *result = NULL;
285 | long temp;
286 |
287 | va = JLEvaluate(context, args->next);
288 | if(va == NULL || va->tag != JLVALUE_NUMBER) {
289 | InvalidArgumentError(context, args);
290 | goto mod_done;
291 | }
292 | vb = JLEvaluate(context, args->next->next);
293 | if(vb == NULL || vb->tag != JLVALUE_NUMBER) {
294 | InvalidArgumentError(context, args);
295 | goto mod_done;
296 | }
297 | if(args->next->next->next) {
298 | TooManyArgumentsError(context, args);
299 | goto mod_done;
300 | }
301 | temp = (long)vb->value.number;
302 | if(temp == 0) {
303 | goto mod_done;
304 | }
305 |
306 | result = JLDefineNumber(context, NULL, (long)va->value.number % temp);
307 |
308 | mod_done:
309 |
310 | JLRelease(context, va);
311 | JLRelease(context, vb);
312 | return result;
313 |
314 | }
315 |
316 | JLValue *AndFunc(JLContext *context, JLValue *args, void *extra)
317 | {
318 | JLValue *vp;
319 | for(vp = args->next; vp; vp = vp->next) {
320 | if(!CheckCondition(context, vp)) {
321 | return NULL;
322 | }
323 | }
324 | return JLDefineNumber(context, NULL, 1.0);
325 | }
326 |
327 | JLValue *OrFunc(JLContext *context, JLValue *args, void *extra)
328 | {
329 | JLValue *vp;
330 | for(vp = args->next; vp; vp = vp->next) {
331 | if(CheckCondition(context, vp)) {
332 | return JLDefineNumber(context, NULL, 1.0);
333 | }
334 | }
335 | return NULL;
336 | }
337 |
338 | JLValue *NotFunc(JLContext *context, JLValue *args, void *extra)
339 | {
340 | if(args->next == NULL) {
341 | TooFewArgumentsError(context, args);
342 | return NULL;
343 | }
344 | if(args->next->next != NULL) {
345 | TooManyArgumentsError(context, args);
346 | return NULL;
347 | }
348 | if(!CheckCondition(context, args->next)) {
349 | return JLDefineNumber(context, NULL, 1.0);
350 | } else {
351 | return NULL;
352 | }
353 | }
354 |
355 | JLValue *BeginFunc(JLContext *context, JLValue *args, void *extra)
356 | {
357 | JLValue *vp;
358 | JLValue *result = NULL;
359 | JLEnterScope(context);
360 | for(vp = args->next; vp; vp = vp->next) {
361 | JLRelease(context, result);
362 | result = JLEvaluate(context, vp);
363 | }
364 | JLLeaveScope(context);
365 | return result;
366 | }
367 |
368 | JLValue *ConsFunc(JLContext *context, JLValue *args, void *extra)
369 | {
370 | JLValue *head = NULL;
371 | JLValue *rest = NULL;
372 | JLValue *temp = NULL;
373 | JLValue *result = NULL;
374 |
375 | if(args->next == NULL || args->next->next == NULL) {
376 | TooFewArgumentsError(context, args);
377 | return NULL;
378 | }
379 | if(args->next->next->next) {
380 | TooManyArgumentsError(context, args);
381 | return NULL;
382 | }
383 |
384 | rest = JLEvaluate(context, args->next->next);
385 | if(rest != NULL && rest->tag != JLVALUE_LIST) {
386 | InvalidArgumentError(context, args);
387 | JLRelease(context, rest);
388 | return NULL;
389 | }
390 |
391 | temp = JLEvaluate(context, args->next);
392 | head = CopyValue(context, temp);
393 | JLRelease(context, temp);
394 |
395 | result = CreateValue(context, NULL, JLVALUE_LIST);
396 | if(rest) {
397 | head->next = rest->value.lst;
398 | JLRetain(context, rest->value.lst);
399 | JLRelease(context, rest);
400 | }
401 | result->value.lst = head;
402 |
403 | return result;
404 | }
405 |
406 | JLValue *DefineFunc(JLContext *context, JLValue *args, void *extra)
407 | {
408 | JLValue *vp = args->next;
409 | JLValue *result = NULL;
410 | if(vp == NULL) {
411 | TooFewArgumentsError(context, args);
412 | return NULL;
413 | }
414 | if(vp->tag != JLVALUE_VARIABLE) {
415 | InvalidArgumentError(context, args);
416 | return NULL;
417 | }
418 | result = JLEvaluate(context, vp->next);
419 | JLDefineValue(context, vp->value.str, result);
420 | return result;
421 | }
422 |
423 | JLValue *HeadFunc(JLContext *context, JLValue *args, void *extra)
424 | {
425 | JLValue *result = NULL;
426 | JLValue *vp = JLEvaluate(context, args->next);
427 |
428 | if(vp == NULL || vp->tag != JLVALUE_LIST) {
429 | InvalidArgumentError(context, args);
430 | goto head_done;
431 | }
432 |
433 | result = vp->value.lst;
434 | JLRetain(context, result);
435 |
436 | head_done:
437 |
438 | JLRelease(context, vp);
439 | return result;
440 | }
441 |
442 | JLValue *IfFunc(JLContext *context, JLValue *args, void *extra)
443 | {
444 | JLValue *vp = args->next;
445 | if(CheckCondition(context, vp)) {
446 | return JLEvaluate(context, vp->next);
447 | } else if(vp->next) {
448 | return JLEvaluate(context, vp->next->next);
449 | }
450 | return NULL;
451 | }
452 |
453 | JLValue *LambdaFunc(JLContext *context, JLValue *args, void *extra)
454 | {
455 | JLValue *result;
456 | JLValue *scope;
457 |
458 | if(args->next == NULL || args->next->next == NULL) {
459 | TooFewArgumentsError(context, args);
460 | return NULL;
461 | }
462 |
463 | scope = CreateValue(context, NULL, JLVALUE_SCOPE);
464 | scope->value.scope = context->scope;
465 | context->scope->count += 1;
466 |
467 | result = CreateValue(context, NULL, JLVALUE_LAMBDA);
468 | result->value.lst = scope;
469 | result->value.lst->next = args->next;
470 | JLRetain(context, args->next);
471 |
472 | return result;
473 | }
474 |
475 | JLValue *ListFunc(JLContext *context, JLValue *args, void *extra)
476 | {
477 | JLValue *result = NULL;
478 | if(args->next) {
479 | result = CreateValue(context, NULL, JLVALUE_LIST);
480 | JLValue **item = &result->value.lst;
481 | JLValue *vp;
482 | for(vp = args->next; vp; vp = vp->next) {
483 | JLValue *arg = JLEvaluate(context, vp);
484 | JLValue *temp = CopyValue(context, arg);
485 | *item = temp;
486 | item = &temp->next;
487 | JLRelease(context, arg);
488 | }
489 | }
490 | return result;
491 | }
492 |
493 | JLValue *RestFunc(JLContext *context, JLValue *args, void *extra)
494 | {
495 | JLValue *result = NULL;
496 | JLValue *vp = JLEvaluate(context, args->next);
497 |
498 | if(vp == NULL || vp->tag != JLVALUE_LIST) {
499 | InvalidArgumentError(context, args);
500 | goto rest_done;
501 | }
502 |
503 | if(vp->value.lst && vp->value.lst->next) {
504 | result = CreateValue(context, NULL, JLVALUE_LIST);
505 | result->value.lst = vp->value.lst->next;
506 | JLRetain(context, result->value.lst);
507 | }
508 |
509 | rest_done:
510 |
511 | JLRelease(context, vp);
512 | return result;
513 | }
514 |
515 | JLValue *SubstrFunc(JLContext *context, JLValue *args, void *extra)
516 | {
517 | JLValue *result = NULL;
518 | JLValue *str = NULL;
519 | JLValue *sval = NULL;
520 | JLValue *lval = NULL;
521 | size_t start = 0;
522 | size_t len = (size_t)-1;
523 | size_t slen;
524 |
525 | str = JLEvaluate(context, args->next);
526 | if(!str || str->tag != JLVALUE_STRING) {
527 | InvalidArgumentError(context, args);
528 | goto substr_done;
529 | }
530 |
531 | sval = JLEvaluate(context, args->next->next);
532 | if(sval) {
533 | if(sval->tag != JLVALUE_NUMBER) {
534 | InvalidArgumentError(context, args);
535 | goto substr_done;
536 | }
537 | start = (size_t)sval->value.number;
538 | }
539 |
540 | if(args->next->next) {
541 | if(args->next->next->next && args->next->next->next->next) {
542 | TooManyArgumentsError(context, args);
543 | goto substr_done;
544 | }
545 | lval = JLEvaluate(context, args->next->next->next);
546 | if(lval) {
547 | if(lval->tag != JLVALUE_NUMBER) {
548 | InvalidArgumentError(context, args);
549 | goto substr_done;
550 | }
551 | len = (size_t)lval->value.number;
552 | }
553 | }
554 |
555 | slen = strlen(str->value.str);
556 | if(start < slen && len > 0) {
557 | len = slen - start > len ? len : slen - start;
558 | result = CreateValue(context, NULL, JLVALUE_STRING);
559 | result->value.str = (char*)malloc(len + 1);
560 | memcpy(result->value.str, &str->value.str[start], len);
561 | result->value.str[len] = 0;
562 | }
563 |
564 | substr_done:
565 |
566 | JLRelease(context, str);
567 | JLRelease(context, sval);
568 | JLRelease(context, lval);
569 |
570 | return result;
571 |
572 | }
573 |
574 | JLValue *ConcatFunc(JLContext *context, JLValue *args, void *extra)
575 | {
576 | JLValue *result = CreateValue(context, NULL, JLVALUE_STRING);
577 | JLValue *vp;
578 | size_t len = 0;
579 | size_t max_len = 8;
580 | result->value.str = (char*)malloc(max_len);
581 | for(vp = args->next; vp; vp = vp->next) {
582 | JLValue *arg = JLEvaluate(context, vp);
583 | if(arg == NULL || arg->tag != JLVALUE_STRING) {
584 | InvalidArgumentError(context, args);
585 | JLRelease(context, arg);
586 | JLRelease(context, result);
587 | return NULL;
588 | } else {
589 | const size_t l = strlen(arg->value.str);
590 | const size_t new_len = len + l;
591 | if(new_len >= max_len) {
592 | max_len = new_len + 1;
593 | result->value.str = (char*)realloc(result->value.str, max_len);
594 | }
595 | memcpy(&result->value.str[len], arg->value.str, l);
596 | len = new_len;
597 | }
598 | JLRelease(context, arg);
599 | }
600 | result->value.str[len] = 0;
601 | return result;
602 | }
603 |
604 | JLValue *IsNumberFunc(JLContext *context, JLValue *args, void *extra)
605 | {
606 | JLValue *arg = NULL;
607 | JLValue *result = NULL;
608 |
609 | if(args->next == NULL) {
610 | TooFewArgumentsError(context, args);
611 | return NULL;
612 | }
613 | if(args->next->next) {
614 | TooManyArgumentsError(context, args);
615 | return NULL;
616 | }
617 |
618 | arg = JLEvaluate(context, args->next);
619 | if(arg && arg->tag == JLVALUE_NUMBER) {
620 | result = JLDefineNumber(context, NULL, 1.0);
621 | }
622 | JLRelease(context, arg);
623 | return result;
624 | }
625 |
626 | JLValue *IsStringFunc(JLContext *context, JLValue *args, void *extra)
627 | {
628 | JLValue *arg = NULL;
629 | JLValue *result = NULL;
630 |
631 | if(args->next == NULL) {
632 | TooFewArgumentsError(context, args);
633 | return NULL;
634 | }
635 | if(args->next->next) {
636 | TooManyArgumentsError(context, args);
637 | return NULL;
638 | }
639 |
640 | arg = JLEvaluate(context, args->next);
641 | if(arg && arg->tag == JLVALUE_STRING) {
642 | result = JLDefineNumber(context, NULL, 1.0);
643 | }
644 | JLRelease(context, arg);
645 | return result;
646 |
647 | }
648 |
649 | JLValue *IsListFunc(JLContext *context, JLValue *args, void *extra)
650 | {
651 | JLValue *arg = NULL;
652 | JLValue *result = NULL;
653 |
654 | if(args->next == NULL) {
655 | TooFewArgumentsError(context, args);
656 | return NULL;
657 | }
658 | if(args->next->next) {
659 | TooManyArgumentsError(context, args);
660 | return NULL;
661 | }
662 |
663 | arg = JLEvaluate(context, args->next);
664 | if(arg && arg->tag == JLVALUE_LIST) {
665 | result = JLDefineNumber(context, NULL, 1.0);
666 | }
667 | JLRelease(context, arg);
668 | return result;
669 | }
670 |
671 | JLValue *IsNullFunc(JLContext *context, JLValue *args, void *extra)
672 | {
673 | JLValue *arg = NULL;
674 |
675 | if(args->next == NULL) {
676 | TooFewArgumentsError(context, args);
677 | return NULL;
678 | }
679 | if(args->next->next) {
680 | TooManyArgumentsError(context, args);
681 | return NULL;
682 | }
683 |
684 | arg = JLEvaluate(context, args->next);
685 | if(arg == NULL) {
686 | return JLDefineNumber(context, NULL, 1.0);
687 | } else {
688 | JLRelease(context, arg);
689 | return NULL;
690 | }
691 | }
692 |
693 | void RegisterFunctions(JLContext *context)
694 | {
695 | size_t i;
696 | for(i = 0; i < INTERNAL_FUNCTION_COUNT; i++) {
697 | JLDefineSpecial(context, INTERNAL_FUNCTIONS[i].name,
698 | INTERNAL_FUNCTIONS[i].function, NULL);
699 | }
700 | }
701 |
702 |
--------------------------------------------------------------------------------
/src/jl-func.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-func.h
3 | * @author Joe Wingbermuehle
4 | *
5 | * Built-in JL functions.
6 | *
7 | */
8 |
9 | #ifndef JL_FUNC_H
10 | #define JL_FUNC_H
11 |
12 | struct JLContext;
13 |
14 | void RegisterFunctions(struct JLContext *context);
15 |
16 | #endif /* JL_FUNC_H */
17 |
--------------------------------------------------------------------------------
/src/jl-scope.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-scope.c
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #include "jl-scope.h"
7 | #include "jl-context.h"
8 | #include "jl-value.h"
9 |
10 | #include
11 | #include
12 |
13 | static unsigned int CountScopeBindings(BindingNode *binding, ScopeNode *scope);
14 | static void ReleaseBindings(JLContext *context, BindingNode *binding);
15 |
16 | unsigned int CountScopeBindings(BindingNode *binding, ScopeNode *scope)
17 | {
18 | unsigned int count = 0;
19 | if(binding) {
20 | count += CountScopeBindings(binding->left, scope);
21 | count += CountScopeBindings(binding->right, scope);
22 | if(binding->value &&
23 | binding->value->tag == JLVALUE_LAMBDA &&
24 | binding->value->count == 1) {
25 | if(binding->value->value.lst->value.scope == scope) {
26 | count += 1;
27 | }
28 | }
29 | }
30 | return count;
31 | }
32 |
33 | void ReleaseBindings(JLContext *context, BindingNode *binding)
34 | {
35 | if(binding) {
36 | ReleaseBindings(context, binding->left);
37 | ReleaseBindings(context, binding->right);
38 | free(binding->name);
39 | JLRelease(context, binding->value);
40 | PutFree(context, binding);
41 | }
42 | }
43 |
44 | void JLEnterScope(JLContext *context)
45 | {
46 | ScopeNode *scope = (ScopeNode*)GetFree(context);
47 | scope->count = 1;
48 | scope->bindings = NULL;
49 | scope->next = context->scope;
50 | context->scope = scope;
51 | }
52 |
53 | void JLLeaveScope(JLContext *context)
54 | {
55 | ScopeNode *scope = context->scope;
56 | context->scope = scope->next;
57 | ReleaseScope(context, scope);
58 | }
59 |
60 | void ReleaseScope(JLContext *context, ScopeNode *scope)
61 | {
62 | const unsigned int new_count = scope->count - 1
63 | - CountScopeBindings(scope->bindings, scope);
64 | if(new_count == 0) {
65 | ReleaseBindings(context, scope->bindings);
66 | PutFree(context, scope);
67 | } else {
68 | scope->count -= 1;
69 | }
70 | }
71 |
72 | JLValue *Lookup(JLContext *context, const char *name)
73 | {
74 | const ScopeNode *scope = context->scope;
75 | while(scope) {
76 | const BindingNode *binding = scope->bindings;
77 | while(binding) {
78 | const int v = strcmp(binding->name, name);
79 | if(v < 0) {
80 | binding = binding->left;
81 | } else if(v > 0) {
82 | binding = binding->right;
83 | } else {
84 | return binding->value;
85 | }
86 | }
87 | scope = scope->next;
88 | }
89 | Error(context, "symbol not found: %s", name);
90 | return NULL;
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/src/jl-scope.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-scope.h
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #ifndef JL_SCOPE_H
7 | #define JL_SCOPE_H
8 |
9 | struct JLContext;
10 | struct JLValue;
11 |
12 | typedef struct BindingNode {
13 | char *name;
14 | struct JLValue *value;
15 | struct BindingNode *left;
16 | struct BindingNode *right;
17 | } BindingNode;
18 |
19 | typedef struct ScopeNode {
20 | BindingNode *bindings;
21 | struct ScopeNode *next;
22 | unsigned int count;
23 | } ScopeNode;
24 |
25 | void ReleaseScope(struct JLContext *context, ScopeNode *scope);
26 |
27 | struct JLValue *Lookup(struct JLContext *context, const char *name);
28 |
29 | #endif /* JL_SCOPE_H */
30 |
--------------------------------------------------------------------------------
/src/jl-value.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-value.c
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #include "jl-value.h"
7 | #include "jl-context.h"
8 | #include
9 |
10 | JLValue *CreateValue(JLContext *context, const char *name, JLValueType tag)
11 | {
12 | JLValue *result = (JLValue*)GetFree(context);
13 | result->tag = tag;
14 | result->next = NULL;
15 | result->count = 1;
16 | JLDefineValue(context, name, result);
17 | return result;
18 | }
19 |
20 | JLValue *CopyValue(JLContext *context, const JLValue *other)
21 | {
22 | JLValue *result = NULL;
23 | if(other) {
24 | result = CreateValue(context, NULL, other->tag);
25 | result->value = other->value;
26 | switch(result->tag) {
27 | case JLVALUE_LIST:
28 | case JLVALUE_LAMBDA:
29 | case JLVALUE_SCOPE:
30 | JLRetain(context, result->value.lst);
31 | break;
32 | case JLVALUE_STRING:
33 | case JLVALUE_VARIABLE:
34 | result->value.str = strdup(result->value.str);
35 | break;
36 | default:
37 | break;
38 | }
39 | } else {
40 | result = CreateValue(context, NULL, JLVALUE_NIL);
41 | }
42 | return result;
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/src/jl-value.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl-value.h
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #ifndef JL_VALUE_H
7 | #define JL_VALUE_H
8 |
9 | #include "jl.h"
10 |
11 | /** Possible value types. */
12 | typedef char JLValueType;
13 | #define JLVALUE_NIL 0 /**< Nil. */
14 | #define JLVALUE_NUMBER 1 /**< Literal number. */
15 | #define JLVALUE_STRING 2 /**< Literal string. */
16 | #define JLVALUE_LIST 3 /**< Linked list. */
17 | #define JLVALUE_LAMBDA 4 /**< Lambda function. */
18 | #define JLVALUE_SPECIAL 5 /**< Special form. */
19 | #define JLVALUE_SCOPE 6 /**< A scope (internal use). */
20 | #define JLVALUE_VARIABLE 7 /**< A variable. */
21 |
22 | /** Special function and extra parameter. */
23 | typedef struct SpecialFunction {
24 | JLFunction func;
25 | void *extra;
26 | } SpecialFunction;
27 |
28 | /** Values in the JL environment.
29 | * Note that these are reference counted.
30 | */
31 | typedef struct JLValue {
32 | union {
33 | struct JLValue *lst;
34 | SpecialFunction special;
35 | char *str;
36 | double number;
37 | void *scope;
38 | } value;
39 | struct JLValue *next;
40 | unsigned int count;
41 | JLValueType tag;
42 | } JLValue;
43 |
44 | JLValue *CreateValue(struct JLContext *context,
45 | const char *name, JLValueType tag);
46 |
47 | JLValue *CopyValue(struct JLContext *context, const JLValue *other);
48 |
49 | #endif /* JL_VALUE_H */
50 |
--------------------------------------------------------------------------------
/src/jl.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jl.c
3 | * @author Joe Wingbermuehle
4 | */
5 |
6 | #include "jl.h"
7 | #include "jl-context.h"
8 | #include "jl-value.h"
9 | #include "jl-scope.h"
10 | #include "jl-func.h"
11 |
12 | #include
13 | #include
14 | #include
15 |
16 | static JLValue *EvalLambda(JLContext *context,
17 | const JLValue *lambda,
18 | JLValue *args);
19 | static JLValue *ParseLiteral(JLContext *context, const char **line);
20 | static JLValue *ParseList(JLContext *context, const char **line);
21 | static JLValue *ParseExpression(JLContext *context, const char **line);
22 |
23 | void JLRetain(JLContext *context, JLValue *value)
24 | {
25 | if(value) {
26 | value->count += 1;
27 | }
28 | }
29 |
30 | void JLRelease(JLContext *context, JLValue *value)
31 | {
32 | while(value) {
33 | value->count -= 1;
34 | if(value->count == 0) {
35 | JLValue *next = value->next;
36 | switch(value->tag) {
37 | case JLVALUE_LIST:
38 | case JLVALUE_LAMBDA:
39 | JLRelease(context, value->value.lst);
40 | break;
41 | case JLVALUE_STRING:
42 | case JLVALUE_VARIABLE:
43 | free(value->value.str);
44 | break;
45 | case JLVALUE_SCOPE:
46 | ReleaseScope(context, (ScopeNode*)value->value.scope);
47 | break;
48 | default:
49 | break;
50 | }
51 | PutFree(context, value);
52 | value = next;
53 | } else {
54 | break;
55 | }
56 | }
57 | }
58 |
59 | JLContext *JLCreateContext()
60 | {
61 | JLContext *context = (JLContext*)malloc(sizeof(JLContext));
62 | context->scope = NULL;
63 | context->freelist = NULL;
64 | context->blocks = NULL;
65 | context->line = 1;
66 | context->levels = 0;
67 | context->max_levels = 1 << 15;
68 | context->error = 0;
69 | JLEnterScope(context);
70 | RegisterFunctions(context);
71 | JLDefineValue(context, "nil", NULL);
72 | return context;
73 | }
74 |
75 | void JLDestroyContext(JLContext *context)
76 | {
77 | JLLeaveScope(context);
78 | FreeContext(context);
79 | }
80 |
81 | void JLDefineValue(JLContext *context, const char *name, JLValue *value)
82 | {
83 | if(name) {
84 | BindingNode **root = &context->scope->bindings;
85 | JLRetain(context, value);
86 | while(*root) {
87 | const int v = strcmp((*root)->name, name);
88 | if(v < 0) {
89 | root = &(*root)->left;
90 | } else if(v > 0) {
91 | root = &(*root)->right;
92 | } else {
93 | /* Overwrite the old binding. */
94 | JLRelease(context, (*root)->value);
95 | (*root)->value = value;
96 | return;
97 | }
98 | }
99 |
100 | /* New binding. */
101 | *root = (BindingNode*)GetFree(context);
102 | (*root)->name = strdup(name);
103 | (*root)->value = value;
104 | (*root)->left = NULL;
105 | (*root)->right = NULL;
106 |
107 | }
108 | }
109 |
110 | void JLDefineSpecial(JLContext *context,
111 | const char *name,
112 | JLFunction func,
113 | void *extra)
114 | {
115 | JLValue *result = CreateValue(context, name, JLVALUE_SPECIAL);
116 | result->value.special.func = func;
117 | result->value.special.extra = extra;
118 | JLRelease(context, result);
119 | }
120 |
121 | JLValue *JLDefineNumber(JLContext *context,
122 | const char *name,
123 | double value)
124 | {
125 | JLValue *result = CreateValue(context, name, JLVALUE_NUMBER);
126 | result->value.number = value;
127 | return result;
128 | }
129 |
130 | JLValue *JLEvaluate(JLContext *context, JLValue *value)
131 | {
132 | JLValue *result = NULL;
133 | if(context->levels == 0) {
134 | context->error = 0;
135 | } else if(context->error) {
136 | return NULL;
137 | }
138 | context->levels += 1;
139 | if(value == NULL) {
140 | result = NULL;
141 | } else if(context->levels > context->max_levels) {
142 | Error(context, "maximum evaluation depth exceeded");
143 | result = NULL;
144 | } else if(value->tag == JLVALUE_LIST) {
145 | JLValue *temp = JLEvaluate(context, value->value.lst);
146 | if(temp) {
147 | switch(temp->tag) {
148 | case JLVALUE_SPECIAL:
149 | result = (temp->value.special.func)(context, value->value.lst,
150 | temp->value.special.extra);
151 | break;
152 | case JLVALUE_LAMBDA:
153 | result = EvalLambda(context, temp, value->value.lst);
154 | break;
155 | default:
156 | result = JLEvaluate(context, temp);
157 | break;
158 | }
159 | JLRelease(context, temp);
160 | }
161 | } else if(value->tag == JLVALUE_VARIABLE) {
162 | result = Lookup(context, value->value.str);
163 | JLRetain(context, result);
164 | } else if(value->tag != JLVALUE_NIL) {
165 | result = value;
166 | JLRetain(context, result);
167 | }
168 | context->levels -= 1;
169 | return result;
170 | }
171 |
172 | JLValue *EvalLambda(JLContext *context, const JLValue *lambda, JLValue *args)
173 | {
174 |
175 | JLValue *scope;
176 | JLValue *params;
177 | JLValue *code;
178 | ScopeNode *old_scope;
179 | ScopeNode *new_scope;
180 | JLValue *bp;
181 | JLValue *ap;
182 | JLValue *result;
183 |
184 | /* The value of a lambda is a list containing the following:
185 | * - The scope in which to execute.
186 | * - A list of positional argument bindings.
187 | * - The code to execute (all remaining list items).
188 | * args should be a list of arguments that is the same length as
189 | * the number of parameters to the lambda. */
190 |
191 | /* Make sure the lambda is well-defined. */
192 | if(lambda->value.lst == NULL ||
193 | lambda->value.lst->tag != JLVALUE_SCOPE ||
194 | lambda->value.lst->next == NULL ||
195 | lambda->value.lst->next->tag != JLVALUE_LIST) {
196 | Error(context, "invalid lambda");
197 | return NULL;
198 | }
199 | scope = lambda->value.lst;
200 | params = lambda->value.lst->next->value.lst;
201 | code = lambda->value.lst->next->next;
202 |
203 | /* Insert bindings. */
204 | old_scope = context->scope;
205 | context->scope = (ScopeNode*)scope->value.scope;
206 | JLEnterScope(context);
207 | new_scope = context->scope;
208 | bp = params;
209 | ap = args->next; /* Skip the name */
210 | while(bp) {
211 | if(ap == NULL) {
212 | Error(context, "too few arguments");
213 | result = NULL;
214 | goto done_eval_lambda;
215 | }
216 | if(bp->tag != JLVALUE_VARIABLE) {
217 | Error(context, "invalid lambda argument");
218 | result = NULL;
219 | goto done_eval_lambda;
220 | }
221 | context->scope = old_scope;
222 | if(bp->next == NULL && ap->next != NULL) {
223 |
224 | /* Make the rest of the arguments into a list parameter. */
225 | result = CreateValue(context, NULL, JLVALUE_LIST);
226 | JLValue **item = &result->value.lst;
227 | while(ap) {
228 | *item = JLEvaluate(context, ap);
229 | item = &(*item)->next;
230 | ap = ap->next;
231 | }
232 |
233 | } else {
234 |
235 | /* A single matching parameter. */
236 | result = JLEvaluate(context, ap);
237 | ap = ap->next;
238 |
239 | }
240 | context->scope = new_scope;
241 |
242 | JLDefineValue(context, bp->value.str, result);
243 | JLRelease(context, result);
244 | bp = bp->next;
245 | }
246 |
247 | result = NULL;
248 | while(code) {
249 | result = JLEvaluate(context, code);
250 | code = code->next;
251 | if(code) {
252 | JLRelease(context, result);
253 | }
254 | }
255 |
256 | done_eval_lambda:
257 |
258 | JLLeaveScope(context);
259 | context->scope = old_scope;
260 |
261 | return result;
262 |
263 | }
264 |
265 | JLValue *ParseLiteral(JLContext *context, const char **line)
266 | {
267 |
268 | /* Separators include '(', ')', and white-space.
269 | * If if token starts with '"', we treat it as a string with escape
270 | * characters like in C.
271 | * Otherwise, if a token can be parsed as a number, we treat it as such.
272 | * Everything else we treat as a string.
273 | * Note that function lookups happen later, here we only generate
274 | * strings and floating-point numbers.
275 | */
276 |
277 | JLValue *result = CreateValue(context, NULL, JLVALUE_NIL);
278 |
279 | if(**line == '\"') {
280 | size_t max_len = 16;
281 | size_t len = 0;
282 | char in_control = 0;
283 | char in_hex = 0;
284 | char in_octal = 0;
285 | result->value.str = (char*)malloc(max_len);
286 | *line += 1;
287 | while(**line && (in_control != 0 || **line != '\"')) {
288 | if(len + 1 >= max_len) {
289 | max_len += 16;
290 | result->value.str = (char*)realloc(result->value.str, max_len);
291 | }
292 | if(in_hex) {
293 | /* In a hex control sequence. */
294 | if(**line >= '0' && **line <= '9') {
295 | result->value.str[len] *= 16;
296 | result->value.str[len] += **line - '0';
297 | in_hex -= 1;
298 | *line += 1;
299 | } else if(**line >= 'a' && **line <= 'f') {
300 | result->value.str[len] *= 16;
301 | result->value.str[len] += **line - 'a' + 10;
302 | in_hex -= 1;
303 | *line += 1;
304 | } else if(**line >= 'A' && **line <= 'F') {
305 | result->value.str[len] *= 16;
306 | result->value.str[len] += **line - 'A' + 10;
307 | in_hex -= 1;
308 | *line += 1;
309 | } else {
310 | /* Premature end of hex sequence; reparse this character. */
311 | in_hex = 0;
312 | }
313 | } else if(in_octal) {
314 | /* In an octal control sequence. */
315 | if(**line >= '0' && **line <= '7') {
316 | result->value.str[len] *= 8;
317 | result->value.str[len] += **line - '0';
318 | in_octal -= 1;
319 | *line += 1;
320 | } else {
321 | /* Premature end of octal sequence; reparse this character. */
322 | in_octal = 0;
323 | }
324 | } else if(in_control) {
325 | /* In a control sequence. */
326 | in_control = 0;
327 | switch(**line) {
328 | case 'a': /* bell */
329 | result->value.str[len++] = '\a';
330 | break;
331 | case 'b': /* backspace */
332 | result->value.str[len++] = '\b';
333 | break;
334 | case 'f': /* form-feed */
335 | result->value.str[len++] = '\f';
336 | break;
337 | case 'n': /* new-line */
338 | result->value.str[len++] = '\n';
339 | break;
340 | case 'r': /* carriage return */
341 | result->value.str[len++] = '\r';
342 | break;
343 | case 't': /* tab */
344 | result->value.str[len++] = '\t';
345 | break;
346 | case 'v': /* vertical tab */
347 | result->value.str[len++] = '\v';
348 | break;
349 | case 'x': /* Hex control sequence. */
350 | in_hex = 2;
351 | break;
352 | case '0': /* Octal control sequence. */
353 | in_octal = 3;
354 | break;
355 | default: /* Literal character */
356 | result->value.str[len++] = **line;
357 | break;
358 | }
359 | *line += 1;
360 | } else if(**line == '\\') {
361 | /* Start of a control sequence. */
362 | in_control = 1;
363 | *line += 1;
364 | } else {
365 | /* Regular character. */
366 | result->value.str[len] = **line;
367 | len += 1;
368 | *line += 1;
369 | }
370 | }
371 | result->value.str[len] = 0;
372 | result->tag = JLVALUE_STRING;
373 | if(**line) {
374 | /* Skip the terminating '"'. */
375 | *line += 1;
376 | }
377 | } else {
378 |
379 | const char *start = *line;
380 | char *end;
381 | size_t len = 0;
382 |
383 | /* Determine how long this token is. */
384 | while(**line != 0 && **line != '(' && **line != ')' &&
385 | **line != ' ' && **line != '\t' && **line != '\r' &&
386 | **line != '\n' && **line != ';') {
387 | len += 1;
388 | *line += 1;
389 | }
390 |
391 | /* Attempt to parse the token as a double. */
392 | result->value.number = strtod(start, &end);
393 |
394 | /* If we couldn't parse the whole thing, treat it as a variable. */
395 | if(start + len != end) {
396 | result->tag = JLVALUE_VARIABLE;
397 | result->value.str = (char*)malloc(len + 1);
398 | memcpy(result->value.str, start, len);
399 | result->value.str[len] = 0;
400 | } else {
401 | result->tag = JLVALUE_NUMBER;
402 | }
403 |
404 | }
405 |
406 | return result;
407 |
408 | }
409 |
410 | JLValue *ParseList(JLContext *context, const char **line)
411 | {
412 |
413 | JLValue *result;
414 | JLValue **item;
415 |
416 | *line += 1; /* Skip '(' */
417 |
418 | result = CreateValue(context, NULL, JLVALUE_LIST);
419 | result->value.lst = NULL;
420 | item = &result->value.lst;
421 |
422 | while(**line && **line != ')') {
423 | JLValue *temp = ParseExpression(context, line);
424 | if(temp == NULL) {
425 | break;
426 | }
427 | *item = temp;
428 | item = &(*item)->next;
429 | }
430 |
431 | if(**line != ')') {
432 | Error(context, "expected ')'");
433 | JLRelease(context, result);
434 | return NULL;
435 | }
436 |
437 | *line += 1; /* Skip ')' */
438 |
439 | return result;
440 |
441 | }
442 |
443 | JLValue *ParseExpression(JLContext *context, const char **line)
444 | {
445 | /* Skip leading white-space. */
446 | for(;;) {
447 | if(**line == ';') {
448 | while(**line && **line != '\n') {
449 | *line += 1;
450 | }
451 | } else if(**line == '\n') {
452 | context->line += 1;
453 | } else if( **line != '\t' &&
454 | **line != ' ' &&
455 | **line != '\r') {
456 | break;
457 | }
458 | *line += 1;
459 | }
460 |
461 | switch(**line) {
462 | case 0:
463 | case ')':
464 | return NULL;
465 | case '(':
466 | return ParseList(context, line);
467 | default:
468 | return ParseLiteral(context, line);
469 | }
470 | }
471 |
472 | JLValue *JLParse(JLContext *context, const char **line)
473 | {
474 | JLValue *result = ParseExpression(context, line);
475 | if(**line == ')') {
476 | Error(context, "unexpected ')'");
477 | *line += 1;
478 | }
479 | return result;
480 | }
481 |
482 | char JLIsNumber(JLValue *value)
483 | {
484 | if(value && value->tag == JLVALUE_NUMBER) {
485 | return 1;
486 | } else {
487 | return 0;
488 | }
489 | }
490 |
491 | double JLGetNumber(JLValue *value)
492 | {
493 | return value->value.number;
494 | }
495 |
496 | char JLIsString(JLValue *value)
497 | {
498 | if(value && value->tag == JLVALUE_STRING) {
499 | return 1;
500 | } else {
501 | return 0;
502 | }
503 | }
504 |
505 | const char *JLGetString(JLValue *value)
506 | {
507 | return value->value.str;
508 | }
509 |
510 | char JLIsList(JLValue *value)
511 | {
512 | if(value && value->tag == JLVALUE_LIST) {
513 | return 1;
514 | } else {
515 | return 0;
516 | }
517 | }
518 |
519 | JLValue *JLGetHead(JLValue *value)
520 | {
521 | return value->value.lst;
522 | }
523 |
524 | JLValue *JLGetNext(JLValue *value)
525 | {
526 | return value->next;
527 | }
528 |
529 | void JLPrint(const JLContext *context, const JLValue *value)
530 | {
531 | JLValue *temp;
532 | if(value == NULL || value->tag == JLVALUE_NIL) {
533 | printf("nil");
534 | return;
535 | }
536 | switch(value->tag) {
537 | case JLVALUE_NUMBER:
538 | printf("%g", value->value.number);
539 | break;
540 | case JLVALUE_STRING:
541 | printf("\"%s\"", value->value.str);
542 | break;
543 | case JLVALUE_LIST:
544 | printf("(");
545 | for(temp = value->value.lst; temp; temp = temp->next) {
546 | JLPrint(context, temp);
547 | if(temp->next) {
548 | printf(" ");
549 | }
550 | }
551 | printf(")");
552 | break;
553 | case JLVALUE_LAMBDA:
554 | printf("(lambda ");
555 | for(temp = value->value.lst->next; temp; temp = temp->next) {
556 | JLPrint(context, temp);
557 | if(temp->next) {
558 | printf(" ");
559 | }
560 | }
561 | printf(")");
562 | break;
563 | case JLVALUE_SPECIAL:
564 | printf("special@%p(%p)", value->value.special.func,
565 | value->value.special.extra);
566 | break;
567 | case JLVALUE_VARIABLE:
568 | printf("%s", value->value.str);
569 | break;
570 | default:
571 | printf("\n?\n");
572 | break;
573 | }
574 | }
575 |
576 |
--------------------------------------------------------------------------------
/src/jli.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file jli.c
3 | * @authoer Joe Wingbermuehle
4 | *
5 | * This is a REPL for interfacing with JL on the command line.
6 | *
7 | */
8 |
9 | #include "jl.h"
10 |
11 | #include
12 | #include
13 |
14 | static struct JLValue *PrintFunc(struct JLContext *context,
15 | struct JLValue *args,
16 | void *extra)
17 | {
18 | struct JLValue *vp;
19 | for(vp = JLGetNext(args); vp; vp = JLGetNext(vp)) {
20 | struct JLValue *result = JLEvaluate(context, vp);
21 | if(JLIsString(result)) {
22 | printf("%s", JLGetString(result));
23 | } else {
24 | JLPrint(context, result);
25 | }
26 | JLRelease(context, result);
27 | }
28 | return NULL;
29 | }
30 |
31 | static struct JLValue *ProcessBuffer(struct JLContext *context,
32 | const char *line)
33 | {
34 | struct JLValue *result = NULL;
35 | while(*line) {
36 | struct JLValue *value = JLParse(context, &line);
37 | if(value) {
38 | JLRelease(context, result);
39 | result = JLEvaluate(context, value);
40 | JLRelease(context, value);
41 | }
42 | }
43 | return result;
44 | }
45 |
46 | int main(int argc, char *argv[])
47 | {
48 | struct JLContext *context;
49 | struct JLValue *result;
50 | char *line = NULL;
51 | size_t cap = 0;
52 | char *filename = NULL;
53 |
54 | if(argc == 2) {
55 | filename = argv[1];
56 | } else if(argc != 1) {
57 | printf("usage: %s \n", argv[0]);
58 | return -1;
59 | } else {
60 | printf("JL Interpreter v%d.%d\n", JL_VERSION_MAJOR, JL_VERSION_MINOR);
61 | printf("Type ^D to exit\n");
62 | }
63 |
64 | context = JLCreateContext();
65 | JLDefineSpecial(context, "print", PrintFunc, NULL);
66 |
67 | if(filename) {
68 | FILE *fd = fopen(filename, "r");
69 | size_t len = 0;
70 | size_t max_len = 16;
71 | if(!fd) {
72 | printf("ERROR: file \"%s\" not found\n", filename);
73 | return -1;
74 | }
75 | line = (char*)malloc(max_len);
76 | for(;;) {
77 | const int ch = fgetc(fd);
78 | if(ch == EOF) {
79 | line[len] = 0;
80 | break;
81 | }
82 | if(len >= max_len) {
83 | max_len *= 2;
84 | line = (char*)realloc(line, max_len);
85 | }
86 | line[len] = (char)ch;
87 | len += 1;
88 | }
89 | fclose(fd);
90 | result = ProcessBuffer(context, line);
91 | JLRelease(context, result);
92 | } else {
93 | for(;;) {
94 | printf("> "); fflush(stdout);
95 | const ssize_t len = getline(&line, &cap, stdin);
96 | if(len <= 0) {
97 | break;
98 | }
99 | result = ProcessBuffer(context, line);
100 | printf("=> ");
101 | JLPrint(context, result);
102 | printf("\n");
103 | JLRelease(context, result);
104 | }
105 | }
106 | if(line) {
107 | free(line);
108 | }
109 | JLDestroyContext(context);
110 |
111 | return 0;
112 |
113 | }
114 |
115 |
--------------------------------------------------------------------------------