diff --git a/.gitignore b/.gitignore
index bdd460c..b07b2a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,10 +31,18 @@ log*
utils_src/readprintwc/out
-.kdev4/
+*.dump
+
+*.bak
+
+src/io/fopen
+
+hi\.*
.vscode/
-hi.*
+core
+
+.kdev4/
post-scarcity.kdev4
diff --git a/Doxyfile b/Doxyfile
index 955cb32..e283f9a 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -135,7 +135,7 @@ ABBREVIATE_BRIEF = "The $name class" \
# description.
# The default value is: NO.
-ALWAYS_DETAILED_SEC = NO
+ALWAYS_DETAILED_SEC = YES
# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
# inherited members of a class in the documentation of that class as if those
@@ -162,7 +162,7 @@ FULL_PATH_NAMES = YES
# will be relative from the directory where doxygen is started.
# This tag requires that the tag FULL_PATH_NAMES is set to YES.
-STRIP_FROM_PATH =
+STRIP_FROM_PATH = src/
# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
# path mentioned in the documentation of a class, which tells the reader which
@@ -187,7 +187,7 @@ SHORT_NAMES = NO
# description.)
# The default value is: NO.
-JAVADOC_AUTOBRIEF = NO
+JAVADOC_AUTOBRIEF = YES
# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
# line (until the first dot) of a Qt-style comment as the brief description. If
@@ -397,7 +397,7 @@ INLINE_GROUPED_CLASSES = NO
# Man pages) or section (for LaTeX and RTF).
# The default value is: NO.
-INLINE_SIMPLE_STRUCTS = NO
+INLINE_SIMPLE_STRUCTS = YES
# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
# enum is documented as struct, union, or enum with the name of the typedef. So
@@ -578,7 +578,7 @@ SORT_MEMBER_DOCS = YES
# this will also influence the order of the classes in the class list.
# The default value is: NO.
-SORT_BRIEF_DOCS = NO
+SORT_BRIEF_DOCS = YES
# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
# (brief and detailed) documentation of class members so that constructors and
@@ -790,7 +790,7 @@ WARN_LOGFILE = doxy.log
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
-INPUT = src src/arith src/memory src/ops
+INPUT = src
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
@@ -864,7 +864,7 @@ FILE_PATTERNS = *.c \
# be searched for input files as well.
# The default value is: NO.
-RECURSIVE = NO
+RECURSIVE = YES
# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
diff --git a/Makefile b/Makefile
index c368d50..7e5efb4 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ SRC_DIRS ?= ./src
SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c -or -name *.s)
HDRS := $(shell find $(SRC_DIRS) -name *.h)
-OBJS := $(addsuffix .o,$(basename $(SRCS)))
+OBJS := $(addsuffix .o,$(basename $(SRCS)))
DEPS := $(OBJS:.o=.d)
TESTS := $(shell find unit-tests -name *.sh)
@@ -15,13 +15,14 @@ INDENT_FLAGS := -nbad -bap -nbc -br -brf -brs -c33 -cd33 -ncdb -ce -ci4 -cli4 \
-d0 -di1 -nfc1 -i4 -ip0 -l75 -lp -npcs \
-npsl -nsc -nsob -nss -nut -prs -l79 -ts2
-VERSION := "0.0.2"
-
CPPFLAGS ?= $(INC_FLAGS) -MMD -MP -g -DDEBUG
-LDFLAGS := -lm
+LDFLAGS := -lm -lcurl
+DEBUGFLAGS := -g3
+
+all: $(TARGET)
$(TARGET): $(OBJS) Makefile
- $(CC) $(LDFLAGS) $(OBJS) -DVERSION=$(VERSION) -o $@ $(LDFLAGS) $(LOADLIBES) $(LDLIBS)
+ $(CC) $(DEBUGFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LDFLAGS) $(LOADLIBES) $(LDLIBS)
doc: $(SRCS) Makefile Doxyfile
doxygen
@@ -38,7 +39,7 @@ test: $(OBJS) $(TESTS) Makefile
.PHONY: clean
clean:
- $(RM) $(TARGET) $(OBJS) $(DEPS) $(SRC_DIRS)/*~ $(SRC_DIRS)/*/*~ *~
+ $(RM) $(TARGET) $(OBJS) $(DEPS) $(SRC_DIRS)/*~ $(SRC_DIRS)/*/*~ *~ core
repl:
$(TARGET) -p 2> psse.log
diff --git a/README.md b/README.md
index caa6375..145b870 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,36 @@
# Post Scarcity Software System, version 0
-Very Nearly a Big Lisp Environment
+tl,dr: look at the [wiki](wiki).
-tl,dr: look at the [[wiki]].
+## State of play
+
+### Version 0.0.5
+
+Has working Lisp interpreter, more or less complete, with functions and symbols as defined under [[#Bindings currently available]] below. Features include hash maps.
+
+#### Known bugs
+
+At the time of writing, big number arithmetic is completely failing. It has worked in the past, but it doesn't now.
+
+There are ludicrous memory leaks. Essentially the garbage collection strategy isn't yet really working. However, if we are to implement the hypercube architecture in future, a mark and sweep garbage collector will not work, so it's important to get the reference counter working properly.
+
+#### Unknown bugs
+
+There are certainly MANY unknown bugs. Please report those you find.
+
+#### Not yet implemented
+
+1. There is as yet no **compiler**, and indeed it isn't yet certain what a compiler would even mean. Do all nodes in a machine necessarily share the same processor architecture?
+2. There's the beginnings of a narrative about how **namespaces** are going to work, but as yet they aren't really implemented.
+3. There is as yet no implementation of the concept of **users**. Access Control Lists exist but are not used. Related, there's no concept of a **session**.
+4. There is as yet no **multiprocessor architecture**, not even a simulated one. As it is intended that threading will be implemented by handing off parts of a computation to peer processors, this means there no **threads** either.
+5. There's no **user interface** beyond a REPL. There isn't even an **editor**, or **history**.
+6. **Printing to strings** does not work.
+7. The **exception system**, while it does exist, needs to be radically rethought.
+
+### Version 0.0.4
+
+Has working rational number arithmetic, as well as integer and real number arithmetic. The stack is now in vector space, but vector space is not yet properly garbage collected. `defun` does not yet work, so although Lisp functions can be defined the syntax is pretty clunky. So you *can* start to do things with this, but you should probably wait for at least a 0.1.0 release!
## Introduction
@@ -16,6 +44,349 @@ What I'm trying to do now is write a detailed low level specification of the und
Although I describe it as a 'Lisp environment', for reasons explained in Post Scarcity Software that doesn't mean you will program it in Lisp. It means that the underlying representation of things in the system is Lispy, not Unixy.
+## Bindings currently available
+
+The following symbols are bound in the bootstrap layer. It is anticipated that
+
+1. Most of the functions will be overridden by versions of the same function written in Lisp; but
+2. these implementations will remain available in the namespace `/:bootstrap`.
+
+### Values
+
+Note that symbols delimited by asterisks, as in `*in*`, invite rebinding; it is expected, for example, that users will want to rebind input and output streams in their current environment. Rebinding some other symbols, for example `nil`, is unwise.
+
+#### nil
+
+The canonical empty list.
+
+#### t
+
+The canonical true value.
+
+#### \*in\*
+
+The input stream.
+
+#### \*out\*
+
+The output stream.
+
+#### \*log\*
+
+The logging stream (equivalent to `stderr`).
+
+#### \*sink\*
+
+The sink stream (equivalent to `/dev/null`).
+
+#### \*prompt\*
+
+The REPL prompt.
+
+### Functions
+
+#### (absolute *n*)
+
+Return the absolute value of a number.
+
+#### (add *n1* *n2* ...), (+ *n1* *n2* ...)
+
+Return the result of adding together all the (assumed numeric) arguments supplied.
+
+#### (append *s1* *s2* ...)
+
+Return a new sequence comprising all the elements of *s1* followed by all the elements of *s2* and so on for an indefinite number of arguments. All arguments must be sequences of the same type.
+
+#### (apply *f* *s*)
+
+Apply the function *f* to the arguments that form the sequence *s*, and return the result.
+
+#### (assoc *key* *store*)
+
+Return the value associated with *key* in *store*. *key* may be an object of any type, but keywords, symbols and strings are handled most efficiently. *store* may be an [*association list*](#Association_list), or may be a hashmap.
+
+#### (car *s*)
+
+Return the first element of the sequence *s*.
+
+#### (cdr *s*)
+
+Return a sequence of all the elements of the sequence *s* except the first.
+
+#### (close *stream*)
+
+Closes the indicates stream. Returns `nil`.
+
+#### (cons *a* *b*)
+
+Returns a new pair comprising *a* and *b*. If *b* is a list, this has the effect of creating a new list with the element *a* prepended to all the elements of *b*. If *b* is `nil`, this has the effect creating a new list with *a* as the sole element. Otherwise, it just creates a pair.
+
+#### (divide *n1* *n2*), (/ *n1* *n2*)
+
+Divides the number *n1* by the number *n2*. If *n1* and *n2* are both integers, it's likely that the result will be a rational number.
+
+#### (eq *o1* *o2*)
+
+Returns true (`t`) if *o1* and *o2* are identically the same object, else `nil`.
+
+#### (equal *o1* *o2*), (= *o1* *o2*)
+
+Returns true (`t`) if *o1* and *o2* are structurally identical to one another, else `nil`.
+
+#### (exception *message*)
+
+Throws (returns) an exception, with the specified *message*. Note that this doesn't really work at all well, and that it is extremely likely this signature will change.
+
+#### (get-hash *key* *hashmap*)
+
+Like 'assoc', but the store must be a hashmap. Deprecated.
+
+#### (hashmap *n* *f* *store*)
+
+Create a hashmap with *n* buckets, using *f* as its hashing function, and initialised with the key/value pairs from *store*. All arguments are optional; if none are passed, will create an empty hashmap with 32 keys and the default hashing function.
+
+#### (inspect *o*)
+
+Prints detailed structure of the object *o*. Primarily for debugging.
+
+#### (keys *store*)
+
+Returns a list of the keys in *store*, which may be either an [*association list*](#Association_list), or a hashmap.
+
+#### (let *bindings* *form*...)
+
+Evaluates each of the *forms* in an environment to which ally of these *bindings* have been added. *bindings* must be an [*association list*](#Association_list), and, additionally, all keys in *bindings* must be symbols. Values in the association list will be evaluated before being bound, and this is done sequentially, as in the behaviour of Common Lisp `let*` rather than of Common Lisp `let`.
+
+#### (list *o*...)
+
+Returns a list of the values of all of its arguments in sequence.
+
+#### (mapcar *f* *s*)
+
+Applies the function *f* to each element of the sequence *s*, and returns a new sequence of the results.
+
+#### (meta *o*), (metadata *o*)
+
+Returns metadata on *o*.
+
+#### (multiply *n1* *n2* ...), (\* *n1* *n2* ...)
+
+Returns the product of multiplying together all of its numeric arguments.
+
+#### (negative? n1)
+
+Returns `t` if its argument is a negative number, else `nil`.
+
+#### (oblist)
+
+Returns a sequence of all the names bound in the root of the naming system.
+
+#### (open *url* *read?*)
+
+Opens a stream to the specified *url*. If a second argument is present and is non-`nil`, the stream is opened for reading; otherwise, it's opened for writing.
+
+#### (print *o* [*stream*])
+
+Prints the print-name of object *o* to the output stream which is the value of *stream*, or to the value of \*out\* in the current environment if no *stream* is provided.
+
+#### (put! *map* *key* *value*)
+
+Puts *value* as the value of *key* in hashmap *map*, destructively modifying it, and returns the map. Note that in future this will work only if the current user has write access to the specified map.
+
+#### (put-all! *map* *assoc*)
+
+Puts each (+key* . *value*) pair from the association list *assoc* into this *map*, destructively modifying it, and returns the map. Note that in future this will work only if the current user has write access to the specified map.
+
+#### (read [*stream*])
+
+Reads a single Lisp form from the input stream which is the value of *stream*, or from the value of \*in\* in the current environment if no *stream* is provided.
+
+#### (read-char [*stream*])
+
+Return the next character from the stream indicated by *stream*, or from the value of \*in\* in the current environment if no *stream* is provided; further arguments are ignored.
+
+#### (repl [*prompt* *input* *output*))
+
+Initiate a new Read/Eval/Print loop with this *prompt*, reading from this *input* stream and writing to this *output* stream. All arguments are optional and default sensibly if omitted. TODO: doesn't actually work yet.
+
+#### (reverse *seq*)
+
+Return a new sequence of the same type as *seq*, containing the same elements but in the reverse order.
+
+#### (slurp *in*)
+
+Reads all available characters on input stream *in* into a string, and returns the string.
+
+#### (source *fn*)
+
+Should return the source code of the function or special form *fn*, but as we don't yet
+have a compiler, doesn't.
+
+#### (subtract *n1* *n2*), (- *n1* *n2*)
+
+Subtracts the numeric value *n2* from the numeric value *n1*, and returns the difference.
+
+#### (throw *message*)
+
+Throws an exception, with the payload *message*. While *message* is at present most usefully a string, it doesn't have to be. Returns the exception, but as exceptions are handled specially by `eval`, it is returned to the catch block of the nearest `try` expression on the stack.
+
+#### (time [*milliseconds-since-epoch*])
+
+Returns a time object whose value is the specified number of *milliseconds-since-epoch*, where the Post Scarcity Software Environment epoch is 14 billion years prior to the UN*X epoch. If *milliseconds-since-epoch* is not specified, returns a time object representing the UTC time when the function was executed.
+
+#### (type *o*)
+
+Returns a string representing the type -- actually the tag value -- of the object *o*.
+
+### Special forms
+
+#### (cond (test value) ...)
+
+Evaluates a series of *(test value)* clauses in turn until a test returns non-nil, when the corresponding value is returned and further tests are not evaluated. This is the same syntax as Common Lisp's `cond` implementation, and different from Clojure's.
+
+It's conventional in Lisp to have a final clause in a `cond` block with the test `t`; however, since we have keywords which are always truthy, it would be equally valid to use `:else` or `:default` as final fallback tests.
+
+#### (lambda (arg ...) form ...), (λ (arg ...) form ...)
+
+Returns an anonymous fuction which evaluates each of the *form*s sequentially in an environment in which the specified *arg*s are bound, and returns the value of the last such form.
+
+#### (let ((*var* . *val*) ...) form ...)
+
+Evaluates each of these *form*s sequentially in an environment in which each *var* is bound to the respective *val* in the bindings specified, and returns the value of the last form.
+
+#### (nlambda (arg ...) form ...), (nλ (arg ...) form ...)
+
+Returns an anonymous special form which evaluates each of the *form*s sequentially in an environment in which the specified *arg*s are bound, and returns the value of the last such form.
+
+#### (progn *f* ...)
+
+Evaluates each of the forms which are its arguments in turn and returns the value of the last.
+
+#### (quote *o*), '*o*
+
+Returns *o*, unevaluated.
+
+#### (set! *name* *value* [*namespace*])
+
+Sets (destructively modifies) the value of *name* this *value* in the root namespace. The *namespace* argument is currently ignored but in future is anticipated to be a path specification of a namespace to be modified.
+
+#### (try (*form* ...) (*handler* ...))
+
+Attempt to evaluate, sequentially, each of the *form*s in the first sequence, and return the value of the last of them; however, if any of them cause an exception to be thrown, then evaluate sequentially each of the *handler*s in the second sequence.
+
+It is recommended that you structure this as follows:
+
+`lisp
+ (try
+ (:body
+ (print "hello")
+ (/ 1 'a)
+ (print "goodbye"))
+ (:catch
+ (print "Well, that failed.")
+ 5))
+`
+
+Here, `:body` and `:catch` are syntactic sugar which will not affect the final value.
+
+### Type values
+
+The following types are known. Further types can be defined, and ultimately it should be possible to define further types in Lisp, but these are what you have to be going on with. Note that where this documentation differs from `memory/consspaceobject.h`, this documentation is *wrong*.
+
+#### CONS
+
+An ordinary cons cell: that is to say, a pair.
+
+#### EXEP
+
+An exception
+
+#### FREE
+
+An unallocated memory cell. User programs should never see this.
+
+#### FUNC
+
+A primitive or compiled Lisp function \-- one whose arguments are pre-evaluated.
+
+#### HASH
+
+A hash map (in vector space)
+
+#### INTR
+
+An arbitrarily large integer number.
+
+#### KEYW
+
+A keyword - an interned, self-evaluating string.
+
+#### LMBA
+
+A lambda cell. Lambdas are the interpretable (source) versions of functions.
+
+#### LOOP
+
+Internal to the workings of the ••loop** function. User functions should never see this.
+
+#### NIL
+
+The special cons cell at address {0,0} whose **car** and **cdr** both point to itself. The canonical empty set. Generally, treated as being indicative of falsity.
+
+#### NLMD
+
+An nlambda cell. NLambdas are the interpretable (source) versions of special forms.
+
+#### RTIO
+
+A rational number, stored as pointers two integers representing dividend and divisor respectively.
+
+#### READ
+
+An open read stream.
+
+#### REAL
+
+A real number, represented internally as an IEEE 754-2008 `binary64`.
+
+#### SPFM
+
+A compiled or primitive special form - one whose arguments are not pre-evaluated but passed as provided.
+
+#### STAK
+
+A stack frame. In vector space.
+
+#### STRG
+
+A string of [UTF-32](https://en.wikipedia.org/wiki/UTF-32) characters, stored as a linked list. Self evaluating.
+
+#### SYMB
+
+A symbol is just like a string except not self-evaluating. Later, there may be some restrictions on what characters are legal in a symbol, but at present there are not.
+
+#### TIME
+
+A time stamp. The epoch for the Post Scarcity Software Environment is 14 billion years before the UN*X epoch, and is chosen as being a reasonable estimate for the birth of the universe, and thus of the start of time.
+
+#### TRUE
+
+The special cell at address {0,1} which is canonically different from NIL.
+
+#### VECP
+
+A pointer to an object in vector space. User functions shouldn't see this, they should see the type of the vector-space object indicated.
+
+#### VECT
+
+A vector of objects. In vector space.
+
+#### WRIT
+
+An open write stream.
+
+
+
## License
Copyright © 2017 [Simon Brooke](mailto:simon@journeyman.cc)
diff --git a/docs/How-do-we-notate-paths.md b/docs/How-do-we-notate-paths.md
new file mode 100644
index 0000000..7cdbcb0
--- /dev/null
+++ b/docs/How-do-we-notate-paths.md
@@ -0,0 +1,89 @@
+# How do we notate paths?
+
+In order to make the namespaces thing work, we need a convenient way to notate paths from the current namespace to a target, or from the root to a target. This is similar to relative and absolute paths in UNIX, except that in PSSE there is no concept of a single or privileged ancestor namespace to the current namespace, so you have no equivalent of `../`.
+
+In this discussion, a **namespace** is just a named, mutable hashmap (but not necessarily mutable by all users; indeed namespaces will almost always be mutable only by selected users. I cannot presently see a justified use for a publicly writable namespace). '**Named**', of a hashmap, merely means there is some path from the privileged root namespace which is the value of `oblist` which leads to that hashmap. A **path** is in principle just a sequence of keys, such that the value of each successive key is bound to a namespace in the namespace bound by its predecessor. The evaluable implementation of paths will be discussed later.
+
+I think also that there must be a privileged **session** namespace, containing information about the current session, which the user can read but not write.
+
+## Security considerations
+
+What's important is that a user cannot rebind privileged names in their own environment. Thus to ensure privileged names, such names must be paths either from the `oblist`or from the current session. So we need magic, privileged notations for these things built into the reader, which cannot be overridden.
+
+This kind of takes away from my general feeling that users should be able to override *anything*. but hey, that's engineering for you.
+
+Users should be able to override reader macros generally; a programmable reader is in the medium term something which should be implemented. But the privileged syntax for paths should not be overridable.
+
+## Current state of the reader
+
+At present, you can rebind the value of the symbol `oblist` in the runtime environment. In principle, you should be able to rebind any symbol. Paths and symbols are not the same.
+
+At present, the colon character is a separator character. So if you type
+
+> (list :foo:bar:ban)
+
+the reader will return
+
+> (list :foo :bar :ban)
+
+That's significant, and helpful.
+
+## Constructing path notation
+
+The Clojure threading macro, `->`, is a useful example of how we can implement this. Essentially it takes a expression as its first argument, passes the value of that expression to the function which is its second argument, the value of that as argument to the function which is its next, and so on. Given that, in Clojure, an expression which has a keyword in the function position and a hashmap in the argument position will return the value of that keyword in that hashmap, this means that, given the hashmap
+
+> (def x {:foo {:bar {:ban "Howzat!!"}}})
+
+the expression
+
+> (-> x :foo :bar :ban)
+
+will return
+
+> "Howzat!!"
+
+So, in general, if we implement the 'keyword in the function position' `eval` hack and the threading macro, then something like
+
+> (-> oblist :users :simon :functions 'foo)
+
+should return the value of the symbol `foo` in the `:functions` of the user called `:simon`.
+
+That's stage one of our goal.
+
+Stage two of our goal is that a stream of non-separator characters separated by colons should be interpreted as a list of keywords. Thus typing
+
+> :foo:bar:ban
+
+should result in not just `:foo`being read, but the list `(:foo :bar :ban)`(? not sure about this)
+
+Stage 3 is to allow a symbol to be appended to a sequence of keywords written by using `/`as a separator, so
+
+> :foo:bar/ban
+
+would be read as `(:foo :bar 'ban)`
+
+Finally, we need privileged notation for root (oblist) and for session. There are very few non-alpha-numeric characters which are available on a standard keyboard and which are not already used as significant lexical characters in Lisp readers. PSSE is not limited, of course, to the characters which are part of the ASCII character set, but it is helpful to use symbols which are reasonably convenient to type, possibly with special keyboard bindings.
+
+So I'm going to propose that the reader should interpret
+
+> /:users:simon:functions/assoc
+
+as
+
+> (-> oblist :users :simon :functions 'assoc)
+
+where `oblist` is the actual privileged global object list, not just the current binding of `oblist` in the environment. Thus, this expression would return my personal version of the function `assoc`, whatever the symbol `assoc` was bound to in the runtime environment.
+
+The use of the leading slash here follows UNIX convention.
+
+I'm going to suggest that the session is referenced by the character §, otherwise known as the 'silcrow'. This is not available on most keyboard mappings, so a custom mapping might be needed, or we might have to fall back on `$`.
+
+Thus the reader should interpret
+
+> §:user
+
+as
+
+> (-> session :user)
+
+where `session`is again a system privileged value, not the binding of `session` in the current environment.
\ No newline at end of file
diff --git a/docs/Hybrid-assoc-lists.md b/docs/Hybrid-assoc-lists.md
new file mode 100644
index 0000000..5bb6ca8
--- /dev/null
+++ b/docs/Hybrid-assoc-lists.md
@@ -0,0 +1,40 @@
+# Hybrid assoc lists
+
+In it's current very prototype stage, PSSE has to forms of name/value store. One is the assoc list, the other is the hashmap.
+
+An assoc (association) list is a list of the form:
+
+ ((name1 . value1)(name2 . value2)(name3 . value3)...)
+
+Hashmaps have many very clear advantages, but assoc lists have one which is very important in the evaluation environment, and that is precisely its sequentiality. Thus, if the same name is bound twice on an assoc list, the value nearest the head is the one which will be recovered:
+
+ (assoc :bar '((:foo . 1) (:bar . "Hello there!")(:ban . 3)(:bar . 2)))
+ => "Hello there!"
+
+Why does this matter? Well, for precisely the same reason it matters when a UNIX system searches for an executable.
+
+Suppose Clare is a user who trusts both Alice and Bob, but she trusts Alice more than Bob. Suppose both Alice and Bob have written implementations of a function called `froboz`. Suppose Clare invokes
+
+ (froboz 3)
+
+Which implementation of `froboz` should be evaluated? An assoc list makes that simple. If Clare binds Alice's implementation into her environment later than Bob's, Alice's will be the one found.
+
+But an assoc list is also fearsomely inefficient, especially if we are in a system with many thousands of names, each of which may be bound multiple times in typical runtime environment.
+
+How to resolve this? How to get some of the benefits of sequential access of assoc lists, with some of the efficiency benefits of hashmaps? What I'm going to propose is a **hybrid assoc list**, that is to say, a list whose members are either
+
+1. (key . value) pairs, or else
+2. hashmaps.
+
+So suppose we have a list, `l`, thus:
+
+ ((:foo . 1) (:bar . 2) {:foo "not this" :ban 3} (:ban . "not this either") (:froboz . 4))
+
+Then:
+
+ (assoc :foo l) => 1
+ (assoc :bar l) => 2
+ (assoc :ban l) => 3
+ (assoc :froboz l) => 4
+
+This will make the implementation of namespaces and search paths vastly easier.
\ No newline at end of file
diff --git a/lisp/defun.lisp b/lisp/defun.lisp
index cec893b..a6d80f5 100644
--- a/lisp/defun.lisp
+++ b/lisp/defun.lisp
@@ -9,6 +9,11 @@
(set (car form) (apply 'lambda (cdr form))))
(t nil))))
+(set! defun!
+ (nlambda
+ form
+ (eval (list 'set! (car form) (cons 'lambda (cdr form))))))
+
(defun! square (x) (* x x))
(set! defsp!
diff --git a/lisp/expt.lisp b/lisp/expt.lisp
new file mode 100644
index 0000000..8b32252
--- /dev/null
+++ b/lisp/expt.lisp
@@ -0,0 +1,8 @@
+(set! expt (lambda
+ (n x)
+ "Return the value of `n` raised to the `x`th power."
+ (cond
+ ((= x 1) n)
+ (t (* n (expt n (- x 1)))))))
+
+(inspect (expt 2 60))
diff --git a/lisp/fact.lisp b/lisp/fact.lisp
index 2f578a6..86d452a 100644
--- a/lisp/fact.lisp
+++ b/lisp/fact.lisp
@@ -1,4 +1,7 @@
(set! fact
(lambda (n)
+ "Compute the factorial of `n`, expected to be an integer."
(cond ((= n 1) 1)
(t (* n (fact (- n 1)))))))
+
+(fact 1000)
diff --git a/lisp/not-working-yet.lisp b/lisp/not-working-yet.lisp
new file mode 100644
index 0000000..0f3a8c2
--- /dev/null
+++ b/lisp/not-working-yet.lisp
@@ -0,0 +1,6 @@
+(set! or (lambda values
+ "True if any of `values` are non-nil."
+ (cond
+ ((nil? values) nil)
+ ((car values) t)
+ (t (eval (cons 'or (cdr values)))))))
diff --git a/lisp/scratchpad.lisp b/lisp/scratchpad.lisp
new file mode 100644
index 0000000..0474099
--- /dev/null
+++ b/lisp/scratchpad.lisp
@@ -0,0 +1,48 @@
+(set! i
+ (+
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000
+ 10000000000000000000))
+
+(set! j (+ i i i i i i i i i i))
+
+(set! k (+ j j j j j j j j j j))
+
+(set! l (+ k k k k k k k k k k))
+
+(set! m (+ l l l l l l l l l l))
+
+(set! n (+ m m m m m m m m m m))
+
+(set! o (+ n n n n n n n n n n))
+
+(set! p (+ o o o o o o o o o o))
+
+(set! q (+ p p p p p p p p p p))
+
+(set! r (+ q q q q q q q q q q))
+
+(set! s (+ r r r r r r r r r r))
+
+(set! t (+ s s s s s s s s s s))
+
+(set! u (+ t t t t t t t t t t))
+
+(set! v (+ u u u u u u u u u u))
+
+(set! x (+ v v v v v v v v v v))
+
+(set! y (+ x x x x x x x x x x))
+
+"we're OK to here: 10^36, which is below the 2^120 barrier so represented as two cells"
+(inspect (set! z (+ y y y y y y y y y y)))
+
+"This blows up: 10^37, which is a three cell bignum."
+(inspect (set! final (+ z z z z z z z z z z)))
diff --git a/lisp/scratchpad2.lisp b/lisp/scratchpad2.lisp
new file mode 100644
index 0000000..65f7aca
--- /dev/null
+++ b/lisp/scratchpad2.lisp
@@ -0,0 +1,85 @@
+"This demonstrates that although the print representation of three cell bignums blows up, the internal representation is sane"
+
+"We start by adding 8 copies of 2^60 - i.e. the first two-cell integer"
+
+(set! a
+ (+
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976
+ 1152921504606846976))
+
+"Then repeatedly add eight copies of the previous generation"
+
+(set! b (+ a a a a a a a a))
+
+(set! c (+ b b b b b b b b))
+
+(set! d (+ c c c c c c c c))
+
+(set! e (+ d d d d d d d d))
+
+(set! f (+ e e e e e e e e))
+
+(set! g (+ f f f f f f f f))
+
+(set! h (+ g g g g g g g g))
+
+(set! i (+ h h h h h h h h))
+
+(set! j (+ i i i i i i i i))
+
+(set! k (+ j j j j j j j j))
+
+(set! l (+ k k k k k k k k))
+
+(set! m (+ l l l l l l l l))
+
+(set! n (+ m m m m m m m m))
+
+(set! o (+ n n n n n n n n))
+
+"p"
+(set! p (+ o o o o o o o o))
+
+"q"
+(set! q (+ p p p p p p p p))
+
+"r"
+(set! r (+ q q q q q q q q))
+
+"s"
+(inspect
+ (set! s (+ r r r r r r r r)))
+
+"t - first three cell integer. Printing blows up here"
+(inspect
+ (set! t (+ s s s s s s s s)))
+
+"u"
+(inspect
+ (set! u (+ t t t t t t t t)))
+
+"v"
+(inspect
+ (set! v (+ u u u u u u u u)))
+
+"w"
+(inspect
+ (set! w (+ v v v v v v v v)))
+
+(inspect
+ (set! x (+ w w w w w w w w)))
+
+(inspect
+ (set! y (+ x x x x x x x x)))
+
+(inspect
+ (set! z (+ y y y y y y y y)))
+
+(inspect
+ (set! final (+ z z z z z z z z)))
diff --git a/lisp/slurp.lisp b/lisp/slurp.lisp
new file mode 100644
index 0000000..e927bcb
--- /dev/null
+++ b/lisp/slurp.lisp
@@ -0,0 +1 @@
+(slurp (set! f (open "http://www.journeyman.cc/")))
diff --git a/lisp/types.lisp b/lisp/types.lisp
new file mode 100644
index 0000000..7f7bf8c
--- /dev/null
+++ b/lisp/types.lisp
@@ -0,0 +1,17 @@
+(set! cons? (lambda (o) "True if o is a cons cell." (= (type o) "CONS") ) )
+(set! exception? (lambda (o) "True if o is an exception." (= (type o) "EXEP")))
+(set! free? (lambda (o) "Trus if o is a free cell - this should be impossible!" (= (type o) "FREE")))
+(set! function? (lambda (o) "True if o is a compiled function." (= (type o) "EXEP")))
+(set! integer? (lambda (o) "True if o is an integer." (= (type o) "INTR")))
+(set! lambda? (lambda (o) "True if o is an interpreted (source) function." (= (type o) "LMDA")))
+(set! nil? (lambda (o) "True if o is the canonical nil value." (= (type o) "NIL ")))
+(set! nlambda? (lambda (o) "True if o is an interpreted (source) special form." (= (type o) "NLMD")))
+(set! rational? (lambda (o) "True if o is an rational number." (= (type o) "RTIO")))
+(set! read? (lambda (o) "True if o is a read stream." (= (type o) "READ") ) )
+(set! real? (lambda (o) "True if o is an real number." (= (type o) "REAL")))
+(set! special? (lambda (o) "True if o is a compiled special form." (= (type o) "SPFM") ) )
+(set! string? (lambda (o) "True if o is a string." (= (type o) "STRG") ) )
+(set! symbol? (lambda (o) "True if o is a symbol." (= (type o) "SYMB") ) )
+(set! true? (lambda (o) "True if o is the canonical true value." (= (type o) "TRUE") ) )
+(set! write? (lambda (o) "True if o is a write stream." (= (type o) "WRIT") ) )
+
diff --git a/notes/bignums.md b/notes/bignums.md
new file mode 100644
index 0000000..f77653c
--- /dev/null
+++ b/notes/bignums.md
@@ -0,0 +1,7 @@
+# All integers are potentially bignums
+
+Each integer comprises at least one cell of type INTR, holding a signed 64 bit integer with a value in the range 0 ... MAX-INTEGER, where the actual value of MAX-INTEGER does not need to be the same as the C language LONG\_MAX, provided that it is less than this. It seems to me that a convenient number would be the largest number less than LONG\_MAX which has all bits set
+
+LONG\_MAX is 0x7FFFFFFFFFFFFFFF, so the number we're looking for is 0x0FFFFFFFFFFFFFFF, which is 1,152,921,504,606,846,975, which is 2^60 - 1. This means we can use bit masking with 0xFFFFFFFFFFFFFFF to extract the part of **int64_t** which will fit in a single cell.
+
+It also means that if we multiply two **int64_t**s into an **__int128_t**, we can then right-shift by 60 places to get the carry.
diff --git a/notes/connection-machine.txt b/notes/connection-machine.md
similarity index 100%
rename from notes/connection-machine.txt
rename to notes/connection-machine.md
diff --git a/notes/mad-software.md b/notes/mad-software.md
new file mode 100644
index 0000000..bbe8092
--- /dev/null
+++ b/notes/mad-software.md
@@ -0,0 +1,75 @@
+# Mad software
+
+I was listening to [Eric Normand's podcast](https://lispcast.com/tension-between-data-and-entity/) this morning, as I was making breakfast and tidying my room; he was talking about semantics and data. It started a train of thought which I shall try to unroll.
+
+I have blogged a lot in the past about madness and about software, but I don't think I've ever blogged about madness and software in the same essay. But the reasons I'm mad and the reasons I'm (sometimes) very good at software are related; both have their roots in autism and dyslexia, or, to put it differently, how my brain is wired.
+
+I first wrote about [post scarcity software](https://blog.journeyman.cc/2006/02/post-scarcity-software.html) thirteen years ago. It was a thought about how software environments should be designed if were weren't held back by the cruft of the past, by tradition and by a lack, frankly, of anything much in the way of new creative thought. And seeing that the core of the system I described is a Lisp, which is to say it builds on a software architecture which is exactly as old as I am, perhaps it is infected by my take on tradition and my own lack of creativity, but let's, for the purposes of this essay, assume not.
+
+I started actually writing the [post scarcity software environment](https://github.com/simon-brooke/post-scarcity) on the second of January 2017, which is to say two years ago. It's been an extremely low priority task, because I don't have enough faith in either my vision or my skill to think that it will ever be of use to anyone. Nevertheless, it does now actually work, in as much as you can write software in it. It's not at all easy yet, and I wouldn't recommend anyone try, but you can check out the master branch from Github, compile it, and it works.
+
+As my mental health has deteriorated, I have been working on it more over the past couple of months, partly because I have lost faith in my ability to deliver the more practical projects I've been working on, and partly because doing something which is genuinely intellectually hard helps subdue the chaos in my mind.
+
+Having said that, it is hard and I am not sharp, and so progress is slow. I started work on big number arithmetic a three weeks ago, and where I'm up to at this point is:
+
+* addition seems to work up to at least the second bignum boundary;
+* multiplication doesn't work beyond the first bignum boundary;
+* subraction doesn't work, and turns out not to be as easy as just inverting addition;
+* division sort of trivially works, but only in the sense that we can create a rational number out of arbitrary bignums;
+* reading works beyond the first bignum boundary, but not up to the second (because multiplication doesn't work);
+* printing doesn't work beyond the first bignum boundary.
+
+I knew bignums were going to be a challenge, and I could have studied other people's bignum code and have consciously chosen not to do so; but this is not fast progress.
+
+(I should point out that in those three weeks I've also done four days of customer work, which is .Net and boring but it's done, spent two days seeing my sister, spent two days so depressed I didn't actually do anything at all, and done a bit or practical work around the croft. But still!)
+
+In a sense, it wasn't expected to be. Writing the underpinnings of a software environment which is conceptually without limits has challenge after challenge after challenge.
+
+But there are ideas in post scarcity which may have wider utility than this mad idea in itself. Layering homogeneities and regularities onto Clojure maps might - perhaps would - make a useful library, might would make a very useful component for exactly the sort of data wrangling Eric Normand was talking about. Yes, you can use a map - raw data soup - to represent a company. But if this map is a member of a homogeneity, 'Companies', then we know every member of it has employees, and that every employee has a salary and an email address. Regularities and homogeneities form the building blocks of APIs; to use the example Eric discussed in his podcast, the salary is the property of the employee, but the payroll is a property of the company. So in post scarcity, you'd get the payroll figure for a company by using a method on the 'Companies' homogeneity. How it computes that value is part of the general doctrine of **'Don't Know, Don't Care'**: the principal that people writing software at any layer in the system do not need to know, and should not need to care, about how things are implemented in the layers below them.
+
+
+
+So, the user needing to find the payroll value would enter something like this:
+
+```
+ (with ((companies . ::shared:pool:companies)
+ (acme . companies:acme-widgets))
+ (companies:methods:payroll acme))
+```
+
+In practice, in post scarcity notation, the payroll method probably looks something like this:
+
+```
+ (lambda (company)
+ (reduce + (map ::shared:pool:employees:methods:salary (:employees company))))
+```
+
+There are issues that I haven't resolved yet about the mutability of regularities and homogeneities; obviously, in order to provide multi-user visibility of current values of shared data, some regularities must be mutable. But mutability has potentially very serious perfomance issues for the hypercube architecture, so I think that in general they should not be.
+
+However, that's detail, and not what I'm trying to talk about here.
+
+What I'm trying to talk about here is the fact that if I were confident that these ideas were any good, and that I had the ability to persuade others that they were any good, it would make far more sense to implement them in Clojure and promote them as a library.
+
+But the problem with depression is that you cannot evaluate whether your ideas are any good. The black dog tells you you're shit, and that your ideas are shit, and that you don't really know enough to be worth listening to, and that you're an old tramp who lives in a hut in the woods, and probably smells, and that in any case interaction with other people quickly makes you shaky and confused, and that you can never get your act together, and you never finish anything.
+
+And all that is objectively true, and I know that it is true. But I also know that I can (or at least have in the past been able to) build really good software, and that I can (or have been able, in the past, to) present ideas really well.
+
+These two collections of statements about me are both true at the same time. But the difference is that I believe the first and I don't believe the second.
+
+And behind all this is the fact that bignum arithmetic is a solved problem. I could dig out the SBCL source code and crib from that. I am bashing my head against bignum arithmetic and trying to solve it myself, not because it's the most efficient way to produce good code quickly, but because what I'm really trying to do is just distract myself and waste time while I can get on with dying.
+
+And the reason beyond that that I'm working on a software system I know I'll never finish, which is designed to run on computers which don't even exist yet - and although I'm very confident that enormously parallel hardware will be used in future, I'm not at all sure it will look anything like what I'm envisaging - the reason I'm building this mad software is that, because it will never be finished, no-one will ever use it except me, and no-one will say how crap it is and how easily it could have been done better.
+
+Because the other thing that I'm doing in writing this stuff, apart from distracting from the swirling chaos and rage in my head, apart from waiting to die, the other thing I'm doing is trying to give myself a feeling of mastery, of competence, of ability to face problems and solve them. And, to an extent, it works. But I have so little confidence that I actually have that mastery, that competence, that I don't want to expose it to criticism. I don't want my few fragile rags of self worth stripped away.
+
+And so I work, and work, and work at something which is so arcane, so obscure, so damned pointless that no-one will ever use it.
+
+Not because I'm even enjoying it, but just to burn the time.
+
+This is mad.
+
+I am mad.
+
+I hate, hate, hate being mad.
+
+Postscript: just writing this essay has made me tearful, headachey, sick, shaky. It's very hard to face up to the irrationalities and self-deceptions in one's own behaviour.
diff --git a/notes/psh-architecture.txt b/notes/psh-architecture.md
similarity index 100%
rename from notes/psh-architecture.txt
rename to notes/psh-architecture.md
diff --git a/src/arith/bignum.c b/src/arith/bignum.c
deleted file mode 100644
index a21a7df..0000000
--- a/src/arith/bignum.c
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * bignum.c
- *
- * Allocation of and operations on arbitrary precision integers.
- *
- * (c) 2018 Simon Brooke
- * Licensed under GPL version 2.0, or, at your option, any later version.
- */
-
-/*
- * Bignums generally follow Knuth, vol 2, 4.3. The word size is 64 bits,
- * and words are stored in individual cons-space objects, comprising the
- * word itself and a pointer to the next word in the number.
- */
diff --git a/src/arith/bignum.h b/src/arith/bignum.h
deleted file mode 100644
index 05c9073..0000000
--- a/src/arith/bignum.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * bignum.h
- *
- * functions for bignum cells.
- *
- *
- * (c) 2017 Simon Brooke
- * Licensed under GPL version 2.0, or, at your option, any later version.
- */
-
-#ifndef __bignum_h
-#define __bignum_h
-
-
-
-#endif
diff --git a/src/arith/integer.c b/src/arith/integer.c
index 5239746..eef171b 100644
--- a/src/arith/integer.c
+++ b/src/arith/integer.c
@@ -8,40 +8,424 @@
*/
#define _GNU_SOURCE
+#include
#include
#include
+#include
+/*
+ * wide characters
+ */
+#include
+#include
-#include "conspage.h"
-#include "consspaceobject.h"
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
#include "debug.h"
+#include "ops/equal.h"
+#include "ops/lispops.h"
+#include "arith/peano.h"
/**
- * return the numeric value of this cell, as a C primitive double, not
- * as a cons-space object. Cell may in principle be any kind of number,
- * but only integers and reals are so far implemented.
+ * hexadecimal digits for printing numbers.
*/
-long double numeric_value( struct cons_pointer pointer ) {
- long double result = NAN;
- struct cons_space_object *cell = &pointer2cell( pointer );
+const char *hex_digits = "0123456789ABCDEF";
- if ( integerp( pointer ) ) {
- result = cell->payload.integer.value * 1.0;
- } else if ( realp( pointer ) ) {
- result = cell->payload.real.value;
+/*
+ * Doctrine from here on in is that ALL integers are bignums, it's just
+ * that integers less than 65 bits are bignums of one cell only.
+ */
+
+/**
+ * Allocate an integer cell representing this `value` and return a cons_pointer to it.
+ * @param value an integer value;
+ * @param more `NIL`, or a pointer to the more significant cell(s) of this number.
+ * *NOTE* that if `more` is not `NIL`, `value` *must not* exceed `MAX_INTEGER`.
+ */
+struct cons_pointer make_integer( int64_t value, struct cons_pointer more ) {
+ struct cons_pointer result = NIL;
+ debug_print( L"Entering make_integer\n", DEBUG_ALLOC );
+
+ if ( integerp( more ) || nilp( more ) ) {
+ result = allocate_cell( INTEGERTV );
+ struct cons_space_object *cell = &pointer2cell( result );
+ cell->payload.integer.value = value;
+ cell->payload.integer.more = more;
+ }
+
+ debug_print( L"make_integer: returning\n", DEBUG_ALLOC );
+ debug_dump_object( result, DEBUG_ALLOC );
+ return result;
+}
+
+/**
+ * Low level integer arithmetic, do not use elsewhere.
+ *
+ * @param c a pointer to a cell, assumed to be an integer cell;
+ * @param op a character representing the operation: expectedto be either
+ * '+' or '*'; behaviour with other values is undefined.
+ * @param is_first_cell true if this is the first cell in a bignum
+ * chain, else false.
+ * \see multiply_integers
+ * \see add_integers
+ */
+__int128_t cell_value( struct cons_pointer c, char op, bool is_first_cell ) {
+ long int val = nilp( c ) ? 0 : pointer2cell( c ).payload.integer.value;
+
+ long int carry = is_first_cell ? 0 : ( MAX_INTEGER + 1 );
+
+ __int128_t result = ( __int128_t ) integerp( c ) ?
+ ( val == 0 ) ? carry : val : op == '*' ? 1 : 0;
+ debug_printf( DEBUG_ARITH,
+ L"cell_value: raw value is %ld, is_first_cell = %s; '%4.4s'; returning ",
+ val, is_first_cell ? "true" : "false",
+ pointer2cell( c ).tag.bytes );
+ debug_print_128bit( result, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ return result;
+}
+
+/**
+ * Overwrite the value field of the integer indicated by `new` with
+ * the least significant 60 bits of `val`, and return the more significant
+ * bits (if any) right-shifted by 60 places. Destructive, primitive, do not
+ * use in any context except primitive operations on integers.
+ *
+ * @param val the value to represent;
+ * @param less_significant the less significant words of this bignum, if any,
+ * else NIL;
+ * @param new a newly created integer, which will be destructively changed.
+ * @return carry, if any, else 0.
+ */
+__int128_t int128_to_integer( __int128_t val,
+ struct cons_pointer less_significant,
+ struct cons_pointer new ) {
+ struct cons_pointer cursor = NIL;
+ __int128_t carry = 0;
+
+ if ( MAX_INTEGER >= val ) {
+ carry = 0;
+ } else {
+ carry = val >> 60;
+ debug_printf( DEBUG_ARITH,
+ L"int128_to_integer: 64 bit overflow; setting carry to %ld\n",
+ ( int64_t ) carry );
+ val &= MAX_INTEGER;
+ }
+
+ struct cons_space_object *newc = &pointer2cell( new );
+ newc->payload.integer.value = val;
+
+ if ( integerp( less_significant ) ) {
+ struct cons_space_object *lsc = &pointer2cell( less_significant );
+ inc_ref( new );
+ lsc->payload.integer.more = new;
+ }
+
+ return carry;
+}
+
+struct cons_pointer make_integer_128( __int128_t val,
+ struct cons_pointer less_significant ) {
+ struct cons_pointer result = NIL;
+
+ do {
+ if ( MAX_INTEGER >= val ) {
+ result = make_integer( ( long int ) val, less_significant );
+ } else {
+ less_significant =
+ make_integer( ( long int ) val & MAX_INTEGER,
+ less_significant );
+ val = val >> 60;
+ }
+
+ } while ( nilp( result ) );
+
+ return result;
+}
+
+/**
+ * Return a pointer to an integer representing the sum of the integers
+ * pointed to by `a` and `b`. If either isn't an integer, will return nil.
+ */
+struct cons_pointer add_integers( struct cons_pointer a,
+ struct cons_pointer b ) {
+ struct cons_pointer result = NIL;
+ struct cons_pointer cursor = NIL;
+
+ debug_print( L"add_integers: a = ", DEBUG_ARITH );
+ debug_print_object( a, DEBUG_ARITH );
+ debug_print( L"; b = ", DEBUG_ARITH );
+ debug_print_object( b, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ __int128_t carry = 0;
+ bool is_first_cell = true;
+
+ if ( integerp( a ) && integerp( b ) ) {
+ debug_print( L"add_integers: \n", DEBUG_ARITH );
+ debug_dump_object( a, DEBUG_ARITH );
+ debug_print( L" plus \n", DEBUG_ARITH );
+ debug_dump_object( b, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ while ( !nilp( a ) || !nilp( b ) || carry != 0 ) {
+ __int128_t av = cell_value( a, '+', is_first_cell );
+ __int128_t bv = cell_value( b, '+', is_first_cell );
+ __int128_t rv = av + bv + carry;
+
+ debug_print( L"add_integers: av = ", DEBUG_ARITH );
+ debug_print_128bit( av, DEBUG_ARITH );
+ debug_print( L"; bv = ", DEBUG_ARITH );
+ debug_print_128bit( bv, DEBUG_ARITH );
+ debug_print( L"; carry = ", DEBUG_ARITH );
+ debug_print_128bit( carry, DEBUG_ARITH );
+ debug_print( L"; rv = ", DEBUG_ARITH );
+ debug_print_128bit( rv, DEBUG_ARITH );
+ debug_print( L"\n", DEBUG_ARITH );
+
+ struct cons_pointer new = make_integer( 0, NIL );
+ carry = int128_to_integer( rv, cursor, new );
+ cursor = new;
+
+ if ( nilp( result ) ) {
+ result = cursor;
+ }
+
+ a = pointer2cell( a ).payload.integer.more;
+ b = pointer2cell( b ).payload.integer.more;
+ is_first_cell = false;
+ }
+ }
+
+ debug_print( L"add_integers returning: ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ return result;
+}
+
+struct cons_pointer base_partial( int depth ) {
+ struct cons_pointer result = NIL;
+
+ for ( int i = 0; i < depth; i++ ) {
+ result = make_integer( 0, result );
}
return result;
}
/**
- * Allocate an integer cell representing this value and return a cons pointer to it.
+ * destructively modify this `partial` by appending this `digit`.
*/
-struct cons_pointer make_integer( int64_t value ) {
- struct cons_pointer result = allocate_cell( INTEGERTAG );
- struct cons_space_object *cell = &pointer2cell( result );
- cell->payload.integer.value = value;
+struct cons_pointer append_digit( struct cons_pointer partial,
+ struct cons_pointer digit ) {
+ struct cons_pointer c = partial;
+ struct cons_pointer result = partial;
- debug_dump_object( result, DEBUG_ARITH );
+ if ( nilp( partial ) ) {
+ result = digit;
+ } else {
+ while ( !nilp( pointer2cell( c ).payload.integer.more ) ) {
+ c = pointer2cell( c ).payload.integer.more;
+ }
+
+ ( &pointer2cell( c ) )->payload.integer.more = digit;
+ }
+ return result;
+}
+
+
+
+/**
+ * Return a pointer to an integer representing the product of the integers
+ * pointed to by `a` and `b`. If either isn't an integer, will return nil.
+ *
+ * Yes, this is one of Muhammad ibn Musa al-Khwarizmi's original recipes, so
+ * you'd think it would be easy; the reason that each step is documented is
+ * because I did not find it so.
+ *
+ * @param a an integer;
+ * @param b an integer.
+ */
+struct cons_pointer multiply_integers( struct cons_pointer a,
+ struct cons_pointer b ) {
+ struct cons_pointer result = make_integer( 0, NIL );
+ bool neg = is_negative( a ) != is_negative( b );
+ bool is_first_b = true;
+ int i = 0;
+
+ debug_print( L"multiply_integers: a = ", DEBUG_ARITH );
+ debug_print_object( a, DEBUG_ARITH );
+ debug_print( L"; b = ", DEBUG_ARITH );
+ debug_print_object( b, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ if ( integerp( a ) && integerp( b ) ) {
+ /* for each digit in a, starting with the least significant (ai) */
+
+ for ( struct cons_pointer ai = a; !nilp( ai );
+ ai = pointer2cell( ai ).payload.integer.more ) {
+ /* set carry to 0 */
+ __int128_t carry = 0;
+
+ /* set least significant digits for result ri for this iteration
+ * to i zeros */
+ struct cons_pointer ri = base_partial( i++ );
+
+ /* for each digit in b, starting with the least significant (bj) */
+ for ( struct cons_pointer bj = b; !nilp( bj );
+ bj = pointer2cell( bj ).payload.integer.more ) {
+
+ debug_printf( DEBUG_ARITH,
+ L"multiply_integers: a[i] = %Ld, b[j] = %Ld, i = %d\n",
+ pointer2cell( ai ).payload.integer.value,
+ pointer2cell( bj ).payload.integer.value, i );
+
+ /* multiply ai with bj and add the carry, resulting in a
+ * value xj which may exceed one digit */
+ __int128_t xj = pointer2cell( ai ).payload.integer.value *
+ pointer2cell( bj ).payload.integer.value;
+ xj += carry;
+
+ /* if xj exceeds one digit, break it into the digit dj and
+ * the carry */
+ carry = xj >> 60;
+ struct cons_pointer dj = make_integer( xj & MAX_INTEGER, NIL );
+
+ /* destructively modify ri by appending dj */
+ ri = append_digit( ri, dj );
+ } /* end for bj */
+
+ /* if carry is not equal to zero, append it as a final digit
+ * to ri */
+ if ( carry != 0 ) {
+ ri = append_digit( ri, make_integer( carry, NIL ) );
+ }
+
+ /* add ri to result */
+ result = add_integers( result, ri );
+
+ debug_print( L"multiply_integers: result is ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+ } /* end for ai */
+ }
+
+ debug_print( L"multiply_integers returning: ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
+ return result;
+}
+
+/**
+ * don't use; private to integer_to_string, and somewaht dodgy.
+ */
+struct cons_pointer integer_to_string_add_digit( int digit, int digits,
+ struct cons_pointer tail ) {
+ wint_t character = btowc( hex_digits[digit] );
+ return ( digits % 3 == 0 ) ?
+ make_string( L',', make_string( character,
+ tail ) ) :
+ make_string( character, tail );
+}
+
+/**
+ * The general principle of printing a bignum is that you print the least
+ * significant digit in whatever base you're dealing with, divide through
+ * by the base, print the next, and carry on until you've none left.
+ * Obviously, that means you print from right to left. Given that we build
+ * strings from right to left, 'printing' an integer to a lisp string
+ * would seem reasonably easy. The problem is when you jump from one integer
+ * object to the next. 64 bit integers don't align with decimal numbers, so
+ * when we get to the last digit from one integer cell, we have potentially
+ * to be looking to the next. H'mmmm.
+ */
+struct cons_pointer integer_to_string( struct cons_pointer int_pointer,
+ int base ) {
+ struct cons_pointer result = NIL;
+
+ if ( integerp( int_pointer ) ) {
+ struct cons_pointer next =
+ pointer2cell( int_pointer ).payload.integer.more;
+ __int128_t accumulator =
+ llabs( pointer2cell( int_pointer ).payload.integer.value );
+ bool is_negative =
+ pointer2cell( int_pointer ).payload.integer.value < 0;
+ int digits = 0;
+
+ if ( accumulator == 0 && nilp( next ) ) {
+ result = c_string_to_lisp_string( L"0" );
+ } else {
+ while ( accumulator > 0 || !nilp( next ) ) {
+ if ( accumulator < MAX_INTEGER && !nilp( next ) ) {
+ accumulator +=
+ ( pointer2cell( next ).payload.integer.value << 60 );
+ next = pointer2cell( next ).payload.integer.more;
+ }
+ int offset = ( int ) ( accumulator % base );
+ debug_printf( DEBUG_IO,
+ L"integer_to_string: digit is %ld, hexadecimal is %c, accumulator is: ",
+ offset, hex_digits[offset] );
+ debug_print_128bit( accumulator, DEBUG_IO );
+ debug_print( L"; result is: ", DEBUG_IO );
+ debug_print_object( result, DEBUG_IO );
+ debug_println( DEBUG_IO );
+
+ result =
+ integer_to_string_add_digit( offset, ++digits, result );
+ accumulator = accumulator / base;
+ }
+
+ if ( stringp( result )
+ && pointer2cell( result ).payload.string.character == L',' ) {
+ /* if the number of digits in the string is divisible by 3, there will be
+ * an unwanted comma on the front. */
+ result = pointer2cell( result ).payload.string.cdr;
+ }
+
+
+ if ( is_negative ) {
+ result = make_string( L'-', result );
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * true if a and be are both integers whose value is the same value.
+ */
+bool equal_integer_integer( struct cons_pointer a, struct cons_pointer b ) {
+ bool result = false;
+
+ if ( integerp( a ) && integerp( b ) ) {
+ struct cons_space_object *cell_a = &pointer2cell( a );
+ struct cons_space_object *cell_b = &pointer2cell( b );
+
+ result =
+ cell_a->payload.integer.value == cell_b->payload.integer.value;
+ }
+
+ return result;
+}
+
+/**
+ * true if `a` is an integer, and `b` is a real number whose value is the
+ * value of that integer.
+ */
+bool equal_integer_real( struct cons_pointer a, struct cons_pointer b ) {
+ bool result = false;
+
+ if ( integerp( a ) && realp( b ) ) {
+ long double bv = pointer2cell( b ).payload.real.value;
+
+ if ( floor( bv ) == bv ) {
+ result = pointer2cell( a ).payload.integer.value == ( int64_t ) bv;
+ }
+ }
return result;
}
diff --git a/src/arith/integer.h b/src/arith/integer.h
index 00b94a6..09a7a83 100644
--- a/src/arith/integer.h
+++ b/src/arith/integer.h
@@ -1,4 +1,4 @@
-/**
+/*
* integer.h
*
* functions for integer cells.
@@ -11,11 +11,22 @@
#ifndef __integer_h
#define __integer_h
-long double numeric_value( struct cons_pointer pointer );
+#include
+#include
-/**
- * Allocate an integer cell representing this value and return a cons pointer to it.
- */
-struct cons_pointer make_integer( int64_t value );
+struct cons_pointer make_integer( int64_t value, struct cons_pointer more );
+
+struct cons_pointer add_integers( struct cons_pointer a,
+ struct cons_pointer b );
+
+struct cons_pointer multiply_integers( struct cons_pointer a,
+ struct cons_pointer b );
+
+struct cons_pointer integer_to_string( struct cons_pointer int_pointer,
+ int base );
+
+bool equal_integer_integer( struct cons_pointer a, struct cons_pointer b );
+
+bool equal_integer_real( struct cons_pointer a, struct cons_pointer b );
#endif
diff --git a/src/arith/peano.c b/src/arith/peano.c
index 9f5e0fb..ae23a00 100644
--- a/src/arith/peano.c
+++ b/src/arith/peano.c
@@ -14,18 +14,19 @@
#include
#include
-#include "consspaceobject.h"
-#include "conspage.h"
+#include "memory/consspaceobject.h"
+#include "memory/conspage.h"
#include "debug.h"
-#include "equal.h"
-#include "integer.h"
-#include "intern.h"
-#include "lispops.h"
-#include "print.h"
-#include "ratio.h"
-#include "read.h"
-#include "real.h"
-#include "stack.h"
+#include "ops/equal.h"
+#include "arith/integer.h"
+#include "ops/intern.h"
+#include "ops/lispops.h"
+#include "arith/peano.h"
+#include "io/print.h"
+#include "arith/ratio.h"
+#include "io/read.h"
+#include "arith/real.h"
+#include "memory/stack.h"
long double to_long_double( struct cons_pointer arg );
int64_t to_long_int( struct cons_pointer arg );
@@ -34,14 +35,23 @@ struct cons_pointer add_2( struct stack_frame *frame,
struct cons_pointer arg1,
struct cons_pointer arg2 );
-
+/**
+ * return true if this `arg` points to a number whose value is zero.
+ */
bool zerop( struct cons_pointer arg ) {
bool result = false;
struct cons_space_object cell = pointer2cell( arg );
switch ( cell.tag.value ) {
- case INTEGERTV:
- result = cell.payload.integer.value == 0;
+ case INTEGERTV:{
+ do {
+ debug_print( L"zerop: ", DEBUG_ARITH );
+ debug_dump_object( arg, DEBUG_ARITH );
+ result =
+ ( pointer2cell( arg ).payload.integer.value == 0 );
+ arg = pointer2cell( arg ).payload.integer.more;
+ } while ( result && integerp( arg ) );
+ }
break;
case RATIOTV:
result = zerop( cell.payload.ratio.dividend );
@@ -55,29 +65,86 @@ bool zerop( struct cons_pointer arg ) {
}
/**
- * TODO: cannot throw an exception out of here, which is a problem
- * if a ratio may legally have zero as a divisor, or something which is
- * not a number is passed in.
+ * does this `arg` point to a negative number?
*/
-long double to_long_double( struct cons_pointer arg ) {
- long double result = 0; /* not a number, as a long double */
+bool is_negative( struct cons_pointer arg ) {
+ bool result = false;
struct cons_space_object cell = pointer2cell( arg );
switch ( cell.tag.value ) {
case INTEGERTV:
- result = ( double ) cell.payload.integer.value;
+ result = cell.payload.integer.value < 0;
break;
case RATIOTV:
- {
- struct cons_space_object dividend =
- pointer2cell( cell.payload.ratio.dividend );
- struct cons_space_object divisor =
- pointer2cell( cell.payload.ratio.divisor );
+ result = is_negative( cell.payload.ratio.dividend );
+ break;
+ case REALTV:
+ result = ( cell.payload.real.value < 0 );
+ break;
+ }
+ return result;
+}
+
+struct cons_pointer absolute( struct cons_pointer arg ) {
+ struct cons_pointer result = NIL;
+ struct cons_space_object cell = pointer2cell( arg );
+
+ if ( is_negative( arg ) ) {
+ switch ( cell.tag.value ) {
+ case INTEGERTV:
result =
- ( long double ) dividend.payload.integer.value /
- divisor.payload.integer.value;
- }
+ make_integer( llabs( cell.payload.integer.value ),
+ cell.payload.integer.more );
+ break;
+ case RATIOTV:
+ result = make_ratio( absolute( cell.payload.ratio.dividend ),
+ cell.payload.ratio.divisor );
+ break;
+ case REALTV:
+ result = make_real( 0 - cell.payload.real.value );
+ break;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Return the closest possible `binary64` representation to the value of
+ * this `arg`, expected to be an integer, ratio or real, or `NAN` if `arg`
+ * is not any of these.
+ *
+ * @arg a pointer to an integer, ratio or real.
+ *
+ * \todo cannot throw an exception out of here, which is a problem
+ * if a ratio may legally have zero as a divisor, or something which is
+ * not a number is passed in.
+ */
+long double to_long_double( struct cons_pointer arg ) {
+ long double result = 0;
+ struct cons_space_object cell = pointer2cell( arg );
+
+ switch ( cell.tag.value ) {
+ case INTEGERTV:
+ // obviously, this doesn't work for bignums
+ result = ( long double ) cell.payload.integer.value;
+ // sadly, this doesn't work at all.
+// result += 1.0;
+// for (bool is_first = false; integerp(arg); is_first = true) {
+// debug_printf(DEBUG_ARITH, L"to_long_double: accumulator = %lf, arg = ", result);
+// debug_dump_object(arg, DEBUG_ARITH);
+// if (!is_first) {
+// result *= (long double)(MAX_INTEGER + 1);
+// }
+// result *= (long double)(cell.payload.integer.value);
+// arg = cell.payload.integer.more;
+// cell = pointer2cell( arg );
+// }
+ break;
+ case RATIOTV:
+ result = to_long_double( cell.payload.ratio.dividend ) /
+ to_long_double( cell.payload.ratio.divisor );
break;
case REALTV:
result = cell.payload.real.value;
@@ -96,7 +163,13 @@ long double to_long_double( struct cons_pointer arg ) {
/**
- * TODO: cannot throw an exception out of here, which is a problem
+ * Return the closest possible `int64_t` representation to the value of
+ * this `arg`, expected to be an integer, ratio or real, or `NAN` if `arg`
+ * is not any of these.
+ *
+ * @arg a pointer to an integer, ratio or real.
+ *
+ * \todo cannot throw an exception out of here, which is a problem
* if a ratio may legally have zero as a divisor, or something which is
* not a number (or is a big number) is passed in.
*/
@@ -105,6 +178,9 @@ int64_t to_long_int( struct cons_pointer arg ) {
struct cons_space_object cell = pointer2cell( arg );
switch ( cell.tag.value ) {
case INTEGERTV:
+ /* \todo if (integerp(cell.payload.integer.more)) {
+ * throw an exception!
+ * } */
result = cell.payload.integer.value;
break;
case RATIOTV:
@@ -119,9 +195,25 @@ int64_t to_long_int( struct cons_pointer arg ) {
/**
-* return a cons_pointer indicating a number which is the sum of
-* the numbers indicated by `arg1` and `arg2`.
-*/
+ * Function: calculate the absolute value of a number.
+ *
+ * (absolute arg)
+ *
+ * @param env the evaluation environment - ignored;
+ * @param frame the stack frame.
+ * @return the absolute value of the number represented by the first
+ * argument, or NIL if it was not a number.
+ */
+struct cons_pointer lisp_absolute( struct stack_frame
+ *frame, struct cons_pointer frame_pointer, struct
+ cons_pointer env ) {
+ return absolute( frame->arg[0] );
+}
+
+/**
+ * return a cons_pointer indicating a number which is the sum of
+ * the numbers indicated by `arg1` and `arg2`.
+ */
struct cons_pointer add_2( struct stack_frame *frame,
struct cons_pointer frame_pointer,
struct cons_pointer arg1,
@@ -131,9 +223,9 @@ struct cons_pointer add_2( struct stack_frame *frame,
struct cons_space_object cell2 = pointer2cell( arg2 );
debug_print( L"add_2( arg1 = ", DEBUG_ARITH );
- debug_print_object( arg1, DEBUG_ARITH );
+ debug_dump_object( arg1, DEBUG_ARITH );
debug_print( L"; arg2 = ", DEBUG_ARITH );
- debug_print_object( arg2, DEBUG_ARITH );
+ debug_dump_object( arg2, DEBUG_ARITH );
debug_print( L"\n", DEBUG_ARITH );
if ( zerop( arg1 ) ) {
@@ -152,12 +244,10 @@ struct cons_pointer add_2( struct stack_frame *frame,
result = arg2;
break;
case INTEGERTV:
- result = make_integer( cell1.payload.integer.value +
- cell2.payload.integer.value );
+ result = add_integers( arg1, arg2 );
break;
case RATIOTV:
- result =
- add_integer_ratio( frame_pointer, arg1, arg2 );
+ result = add_integer_ratio( arg1, arg2 );
break;
case REALTV:
result =
@@ -177,11 +267,10 @@ struct cons_pointer add_2( struct stack_frame *frame,
result = arg2;
break;
case INTEGERTV:
- result =
- add_integer_ratio( frame_pointer, arg2, arg1 );
+ result = add_integer_ratio( arg2, arg1 );
break;
case RATIOTV:
- result = add_ratio_ratio( frame_pointer, arg1, arg2 );
+ result = add_ratio_ratio( arg1, arg2 );
break;
case REALTV:
result =
@@ -219,12 +308,13 @@ struct cons_pointer add_2( struct stack_frame *frame,
* Add an indefinite number of numbers together
* @param env the evaluation environment - ignored;
* @param frame the stack frame.
- * @return a pointer to an integer or real.
+ * @return a pointer to an integer, ratio or real.
+ * @exception if any argument is not a number, returns an exception.
*/
struct cons_pointer lisp_add( struct stack_frame
*frame, struct cons_pointer frame_pointer, struct
cons_pointer env ) {
- struct cons_pointer result = make_integer( 0 );
+ struct cons_pointer result = make_integer( 0, NIL );
struct cons_pointer tmp;
for ( int i = 0;
@@ -253,9 +343,9 @@ struct cons_pointer lisp_add( struct stack_frame
/**
-* return a cons_pointer indicating a number which is the product of
-* the numbers indicated by `arg1` and `arg2`.
-*/
+ * return a cons_pointer indicating a number which is the product of
+ * the numbers indicated by `arg1` and `arg2`.
+ */
struct cons_pointer multiply_2( struct stack_frame *frame,
struct cons_pointer frame_pointer,
struct cons_pointer arg1,
@@ -268,7 +358,7 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
debug_print_object( arg1, DEBUG_ARITH );
debug_print( L"; arg2 = ", DEBUG_ARITH );
debug_print_object( arg2, DEBUG_ARITH );
- debug_print( L")", DEBUG_ARITH );
+ debug_print( L")\n", DEBUG_ARITH );
if ( zerop( arg1 ) ) {
result = arg2;
@@ -285,13 +375,10 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
result = arg2;
break;
case INTEGERTV:
- result = make_integer( cell1.payload.integer.value *
- cell2.payload.integer.value );
+ result = multiply_integers( arg1, arg2 );
break;
case RATIOTV:
- result =
- multiply_integer_ratio( frame_pointer, arg1,
- arg2 );
+ result = multiply_integer_ratio( arg1, arg2 );
break;
case REALTV:
result =
@@ -299,9 +386,12 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
to_long_double( arg2 ) );
break;
default:
- result = throw_exception( c_string_to_lisp_string
- ( L"Cannot multiply: not a number" ),
- frame_pointer );
+ result =
+ throw_exception( make_cons
+ ( c_string_to_lisp_string
+ ( L"Cannot multiply: argument 2 is not a number: " ),
+ c_type( arg2 ) ),
+ frame_pointer );
break;
}
break;
@@ -311,13 +401,10 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
result = arg2;
break;
case INTEGERTV:
- result =
- multiply_integer_ratio( frame_pointer, arg2,
- arg1 );
+ result = multiply_integer_ratio( arg2, arg1 );
break;
case RATIOTV:
- result =
- multiply_ratio_ratio( frame_pointer, arg1, arg2 );
+ result = multiply_ratio_ratio( arg1, arg2 );
break;
case REALTV:
result =
@@ -325,9 +412,12 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
to_long_double( arg2 ) );
break;
default:
- result = throw_exception( c_string_to_lisp_string
- ( L"Cannot multiply: not a number" ),
- frame_pointer );
+ result =
+ throw_exception( make_cons
+ ( c_string_to_lisp_string
+ ( L"Cannot multiply: argument 2 is not a number" ),
+ c_type( arg2 ) ),
+ frame_pointer );
}
break;
case REALTV:
@@ -336,66 +426,67 @@ struct cons_pointer multiply_2( struct stack_frame *frame,
to_long_double( arg2 ) );
break;
default:
- result = throw_exception( c_string_to_lisp_string
- ( L"Cannot multiply: not a number" ),
+ result = throw_exception( make_cons( c_string_to_lisp_string
+ ( L"Cannot multiply: argument 1 is not a number" ),
+ c_type( arg1 ) ),
frame_pointer );
break;
}
}
- debug_print( L" => ", DEBUG_ARITH );
- debug_print_object( arg2, DEBUG_ARITH );
+ debug_print( L"multiply_2 returning: ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
debug_print( L"\n", DEBUG_ARITH );
return result;
}
+#define multiply_one_arg(arg) {if (exceptionp(arg)){result=arg;}else{tmp = result; result = multiply_2( frame, frame_pointer, result, arg ); if ( !eq( tmp, result ) ) dec_ref( tmp );}}
/**
* Multiply an indefinite number of numbers together
* @param env the evaluation environment - ignored;
* @param frame the stack frame.
- * @return a pointer to an integer or real.
+ * @return a pointer to an integer, ratio or real.
+ * @exception if any argument is not a number, returns an exception.
*/
struct cons_pointer lisp_multiply( struct
stack_frame
*frame, struct cons_pointer frame_pointer, struct
cons_pointer env ) {
- struct cons_pointer result = make_integer( 1 );
+ struct cons_pointer result = make_integer( 1, NIL );
struct cons_pointer tmp;
for ( int i = 0; i < args_in_frame && !nilp( frame->arg[i] )
&& !exceptionp( result ); i++ ) {
- tmp = result;
- result = multiply_2( frame, frame_pointer, result, frame->arg[i] );
+ debug_print( L"lisp_multiply: accumulator = ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
+ debug_print( L"; arg = ", DEBUG_ARITH );
+ debug_print_object( frame->arg[i], DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
- if ( !eq( tmp, result ) ) {
- dec_ref( tmp );
- }
+ multiply_one_arg( frame->arg[i] );
}
struct cons_pointer more = frame->more;
while ( consp( more )
&& !exceptionp( result ) ) {
- tmp = result;
- result = multiply_2( frame, frame_pointer, result, c_car( more ) );
-
- if ( !eq( tmp, result ) ) {
- dec_ref( tmp );
- }
-
+ multiply_one_arg( c_car( more ) );
more = c_cdr( more );
}
+ debug_print( L"lisp_multiply returning: ", DEBUG_ARITH );
+ debug_print_object( result, DEBUG_ARITH );
+ debug_println( DEBUG_ARITH );
+
return result;
}
/**
* return a cons_pointer indicating a number which is the
- * inverse of the number indicated by `arg`.
+ * 0 - the number indicated by `arg`.
*/
-struct cons_pointer inverse( struct cons_pointer frame,
- struct cons_pointer arg ) {
+struct cons_pointer negative( struct cons_pointer arg ) {
struct cons_pointer result = NIL;
struct cons_space_object cell = pointer2cell( arg );
@@ -404,16 +495,15 @@ struct cons_pointer inverse( struct cons_pointer frame,
result = arg;
break;
case INTEGERTV:
- result = make_integer( 0 - to_long_int( arg ) );
+ result =
+ make_integer( 0 - cell.payload.integer.value,
+ cell.payload.integer.more );
break;
case NILTV:
result = TRUE;
break;
case RATIOTV:
- result = make_ratio( frame,
- make_integer( 0 -
- to_long_int( cell.payload.
- ratio.dividend ) ),
+ result = make_ratio( negative( cell.payload.ratio.dividend ),
cell.payload.ratio.divisor );
break;
case REALTV:
@@ -429,47 +519,63 @@ struct cons_pointer inverse( struct cons_pointer frame,
/**
- * Subtract one number from another.
+ * Function: is this number negative?
+ *
+ * * (negative? arg)
+ *
* @param env the evaluation environment - ignored;
* @param frame the stack frame.
- * @return a pointer to an integer or real.
+ * @return T if the first argument was a negative number, or NIL if it
+ * was not.
*/
-struct cons_pointer lisp_subtract( struct
- stack_frame
- *frame, struct cons_pointer frame_pointer, struct
- cons_pointer env ) {
- struct cons_pointer result = NIL;
- struct cons_space_object cell0 = pointer2cell( frame->arg[0] );
- struct cons_space_object cell1 = pointer2cell( frame->arg[1] );
+struct cons_pointer lisp_is_negative( struct stack_frame
+ *frame,
+ struct cons_pointer frame_pointer, struct
+ cons_pointer env ) {
+ return is_negative( frame->arg[0] ) ? TRUE : NIL;
+}
- switch ( cell0.tag.value ) {
+
+/**
+ * return a cons_pointer indicating a number which is the result of
+ * subtracting the number indicated by `arg2` from that indicated by `arg1`,
+ * in the context of this `frame`.
+ */
+struct cons_pointer subtract_2( struct stack_frame *frame,
+ struct cons_pointer frame_pointer,
+ struct cons_pointer arg1,
+ struct cons_pointer arg2 ) {
+ struct cons_pointer result = NIL;
+
+ switch ( pointer2cell( arg1 ).tag.value ) {
case EXCEPTIONTV:
- result = frame->arg[0];
+ result = arg1;
break;
case INTEGERTV:
- switch ( cell1.tag.value ) {
+ switch ( pointer2cell( arg2 ).tag.value ) {
case EXCEPTIONTV:
- result = frame->arg[1];
+ result = arg2;
break;
- case INTEGERTV:
- result = make_integer( cell0.payload.integer.value
- - cell1.payload.integer.value );
+ case INTEGERTV:{
+ struct cons_pointer i = negative( arg2 );
+ inc_ref( i );
+ result = add_integers( arg1, i );
+ dec_ref( i );
+ }
break;
case RATIOTV:{
- struct cons_pointer tmp =
- make_ratio( frame_pointer, frame->arg[0],
- make_integer( 1 ) );
+ struct cons_pointer tmp = make_ratio( arg1,
+ make_integer( 1,
+ NIL ) );
inc_ref( tmp );
- result =
- subtract_ratio_ratio( frame_pointer, tmp,
- frame->arg[1] );
+ result = subtract_ratio_ratio( tmp, arg2 );
dec_ref( tmp );
}
break;
case REALTV:
result =
- make_real( to_long_double( frame->arg[0] ) -
- to_long_double( frame->arg[1] ) );
+ make_real( to_long_double( arg1 ) -
+ to_long_double( arg2 ) );
break;
default:
result = throw_exception( c_string_to_lisp_string
@@ -479,30 +585,26 @@ struct cons_pointer lisp_subtract( struct
}
break;
case RATIOTV:
- switch ( cell1.tag.value ) {
+ switch ( pointer2cell( arg2 ).tag.value ) {
case EXCEPTIONTV:
- result = frame->arg[1];
+ result = arg2;
break;
case INTEGERTV:{
- struct cons_pointer tmp =
- make_ratio( frame_pointer, frame->arg[1],
- make_integer( 1 ) );
+ struct cons_pointer tmp = make_ratio( arg2,
+ make_integer( 1,
+ NIL ) );
inc_ref( tmp );
- result =
- subtract_ratio_ratio( frame_pointer, frame->arg[0],
- tmp );
+ result = subtract_ratio_ratio( arg1, tmp );
dec_ref( tmp );
}
break;
case RATIOTV:
- result =
- subtract_ratio_ratio( frame_pointer, frame->arg[0],
- frame->arg[1] );
+ result = subtract_ratio_ratio( arg1, arg2 );
break;
case REALTV:
result =
- make_real( to_long_double( frame->arg[0] ) -
- to_long_double( frame->arg[1] ) );
+ make_real( to_long_double( arg1 ) -
+ to_long_double( arg2 ) );
break;
default:
result = throw_exception( c_string_to_lisp_string
@@ -512,9 +614,8 @@ struct cons_pointer lisp_subtract( struct
}
break;
case REALTV:
- result = exceptionp( frame->arg[1] ) ? frame->arg[1] :
- make_real( to_long_double( frame->arg[0] ) -
- to_long_double( frame->arg[1] ) );
+ result = exceptionp( arg2 ) ? arg2 :
+ make_real( to_long_double( arg1 ) - to_long_double( arg2 ) );
break;
default:
result = throw_exception( c_string_to_lisp_string
@@ -529,10 +630,27 @@ struct cons_pointer lisp_subtract( struct
}
/**
- * Divide one number by another.
+ * Subtract one number from another. If more than two arguments are passed
+ * in the frame, the additional arguments are ignored.
+ * @param env the evaluation environment - ignored;
+ * @param frame the stack frame.
+ * @return a pointer to an integer, ratio or real.
+ * @exception if either argument is not a number, returns an exception.
+ */
+struct cons_pointer lisp_subtract( struct
+ stack_frame
+ *frame, struct cons_pointer frame_pointer, struct
+ cons_pointer env ) {
+ return subtract_2( frame, frame_pointer, frame->arg[0], frame->arg[1] );
+}
+
+/**
+ * Divide one number by another. If more than two arguments are passed
+ * in the frame, the additional arguments are ignored.
* @param env the evaluation environment - ignored;
* @param frame the stack frame.
* @return a pointer to an integer or real.
+ * @exception if either argument is not a number, returns an exception.
*/
struct cons_pointer lisp_divide( struct
stack_frame
@@ -553,23 +671,22 @@ struct cons_pointer lisp_divide( struct
break;
case INTEGERTV:{
struct cons_pointer unsimplified =
- make_ratio( frame_pointer, frame->arg[0],
+ make_ratio( frame->arg[0],
frame->arg[1] );
/* OK, if result may be unsimplified, we should not inc_ref it
* - but if not, we should dec_ref it. */
- result = simplify_ratio( frame_pointer, unsimplified );
+ result = simplify_ratio( unsimplified );
if ( !eq( unsimplified, result ) ) {
dec_ref( unsimplified );
}
}
break;
case RATIOTV:{
- struct cons_pointer one = make_integer( 1 );
+ struct cons_pointer one = make_integer( 1, NIL );
struct cons_pointer ratio =
- make_ratio( frame_pointer, frame->arg[0], one );
- result =
- divide_ratio_ratio( frame_pointer, ratio,
- frame->arg[1] );
+ make_ratio( frame->arg[0], one );
+ inc_ref( ratio );
+ result = divide_ratio_ratio( ratio, frame->arg[1] );
dec_ref( ratio );
}
break;
@@ -591,22 +708,19 @@ struct cons_pointer lisp_divide( struct
result = frame->arg[1];
break;
case INTEGERTV:{
- struct cons_pointer one = make_integer( 1 );
+ struct cons_pointer one = make_integer( 1, NIL );
inc_ref( one );
struct cons_pointer ratio =
- make_ratio( frame_pointer, frame->arg[1], one );
+ make_ratio( frame->arg[1], one );
inc_ref( ratio );
- result =
- divide_ratio_ratio( frame_pointer, frame->arg[0],
- ratio );
+ result = divide_ratio_ratio( frame->arg[0], ratio );
dec_ref( ratio );
dec_ref( one );
}
break;
case RATIOTV:
result =
- divide_ratio_ratio( frame_pointer, frame->arg[0],
- frame->arg[1] );
+ divide_ratio_ratio( frame->arg[0], frame->arg[1] );
break;
case REALTV:
result =
diff --git a/src/arith/peano.h b/src/arith/peano.h
index f1c21b4..3076391 100644
--- a/src/arith/peano.h
+++ b/src/arith/peano.h
@@ -1,4 +1,4 @@
-/**
+/*
* peano.h
*
* Basic peano arithmetic
@@ -12,53 +12,53 @@
#ifndef PEANO_H
#define PEANO_H
-#ifdef __cplusplus
-extern "C" {
-#endif
-
/**
- * Add an indefinite number of numbers together
- * @param env the evaluation environment - ignored;
- * @param frame the stack frame.
- * @return a pointer to an integer or real.
+ * The maximum value we will allow in an integer cell.
*/
- struct cons_pointer
- lisp_add( struct stack_frame *frame, struct cons_pointer frame_pointer,
- struct cons_pointer env );
+#define MAX_INTEGER ((__int128_t)0x0fffffffffffffffL)
-/**
- * Multiply an indefinite number of numbers together
- * @param env the evaluation environment - ignored;
- * @param frame the stack frame.
- * @return a pointer to an integer or real.
- */
- struct cons_pointer
- lisp_multiply( struct stack_frame *frame,
- struct cons_pointer frame_pointer,
- struct cons_pointer env );
+bool zerop( struct cons_pointer arg );
-/**
- * Subtract one number from another.
- * @param env the evaluation environment - ignored;
- * @param frame the stack frame.
- * @return a pointer to an integer or real.
- */
- struct cons_pointer
- lisp_subtract( struct stack_frame *frame,
- struct cons_pointer frame_pointer,
- struct cons_pointer env );
+struct cons_pointer negative( struct cons_pointer arg );
-/**
- * Divide one number by another.
- * @param env the evaluation environment - ignored;
- * @param frame the stack frame.
- * @return a pointer to an integer or real.
- */
- struct cons_pointer
- lisp_divide( struct stack_frame *frame, struct cons_pointer frame_pointer,
- struct cons_pointer env );
+bool is_negative( struct cons_pointer arg );
-#ifdef __cplusplus
-}
-#endif
-#endif /* PEANO_H */
+struct cons_pointer absolute( struct cons_pointer arg );
+
+long double to_long_double( struct cons_pointer arg );
+
+int64_t to_long_int( struct cons_pointer arg );
+
+struct cons_pointer lisp_absolute( struct stack_frame
+ *frame, struct cons_pointer frame_pointer, struct
+ cons_pointer env );
+
+struct cons_pointer
+lisp_add( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+
+struct cons_pointer lisp_is_negative( struct stack_frame
+ *frame,
+ struct cons_pointer frame_pointer, struct
+ cons_pointer env );
+
+struct cons_pointer
+lisp_multiply( struct stack_frame *frame,
+ struct cons_pointer frame_pointer, struct cons_pointer env );
+
+struct cons_pointer negative( struct cons_pointer arg );
+
+struct cons_pointer subtract_2( struct stack_frame *frame,
+ struct cons_pointer frame_pointer,
+ struct cons_pointer arg1,
+ struct cons_pointer arg2 );
+
+struct cons_pointer
+lisp_subtract( struct stack_frame *frame,
+ struct cons_pointer frame_pointer, struct cons_pointer env );
+
+struct cons_pointer
+lisp_divide( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+
+#endif /* PEANO_H */
diff --git a/src/arith/ratio.c b/src/arith/ratio.c
index ca83335..5135d6b 100644
--- a/src/arith/ratio.c
+++ b/src/arith/ratio.c
@@ -11,23 +11,17 @@
#include
#include
-#include "conspage.h"
-#include "consspaceobject.h"
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
#include "debug.h"
-#include "equal.h"
-#include "integer.h"
-#include "lispops.h"
-#include "print.h"
-#include "ratio.h"
+#include "ops/equal.h"
+#include "arith/integer.h"
+#include "ops/lispops.h"
+#include "arith/peano.h"
+#include "io/print.h"
+#include "arith/ratio.h"
-/*
- * declared in peano.c, can't include piano.h here because
- * circularity. TODO: refactor.
- */
-struct cons_pointer inverse( struct cons_pointer frame_pointer,
- struct cons_pointer arg );
-
/**
* return, as a int64_t, the greatest common divisor of `m` and `n`,
*/
@@ -49,51 +43,46 @@ int64_t least_common_multiple( int64_t m, int64_t n ) {
return m / greatest_common_divisor( m, n ) * n;
}
-/**
- * return a cons_pointer indicating a number which is of the
- * same value as the ratio indicated by `arg`, but which may
- * be in a simplified representation. If `arg` isn't a ratio,
- * will throw exception.
- */
-struct cons_pointer simplify_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg ) {
- struct cons_pointer result = arg;
+struct cons_pointer simplify_ratio( struct cons_pointer pointer ) {
+ struct cons_pointer result = pointer;
+ struct cons_space_object cell = pointer2cell( pointer );
+ struct cons_space_object dividend =
+ pointer2cell( cell.payload.ratio.dividend );
+ struct cons_space_object divisor =
+ pointer2cell( cell.payload.ratio.divisor );
- if ( ratiop( arg ) ) {
- int64_t ddrv =
- pointer2cell( pointer2cell( arg ).payload.ratio.dividend ).
- payload.integer.value, drrv =
- pointer2cell( pointer2cell( arg ).payload.ratio.divisor ).
- payload.integer.value, gcd = greatest_common_divisor( ddrv, drrv );
+ if ( divisor.payload.integer.value == 1 ) {
+ result = pointer2cell( pointer ).payload.ratio.dividend;
+ } else {
+ if ( ratiop( pointer ) ) {
+ int64_t ddrv = dividend.payload.integer.value,
+ drrv = divisor.payload.integer.value,
+ gcd = greatest_common_divisor( ddrv, drrv );
- if ( gcd > 1 ) {
- if ( drrv / gcd == 1 ) {
- result = make_integer( ddrv / gcd );
- } else {
- result =
- make_ratio( frame_pointer, make_integer( ddrv / gcd ),
- make_integer( drrv / gcd ) );
+ if ( gcd > 1 ) {
+ if ( drrv / gcd == 1 ) {
+ result = make_integer( ddrv / gcd, NIL );
+ } else {
+ result =
+ make_ratio( make_integer( ddrv / gcd, NIL ),
+ make_integer( drrv / gcd, NIL ) );
+ }
}
}
- } else {
- result =
- throw_exception( make_cons( c_string_to_lisp_string
- ( L"Shouldn't happen: bad arg to simplify_ratio" ),
- arg ), frame_pointer );
}
return result;
-}
+}
/**
* return a cons_pointer indicating a number which is the sum of
- * the ratios indicated by `arg1` and `arg2`. If you pass non-ratios,
- * this is going to break horribly.
+ * the ratios indicated by `arg1` and `arg2`.
+ * @exception will return an exception if either `arg1` or `arg2` is not a
+ * rational number.
*/
-struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer add_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 ) {
struct cons_pointer r, result;
@@ -117,21 +106,21 @@ struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
lcm = least_common_multiple( dr1v, dr2v ),
m1 = lcm / dr1v, m2 = lcm / dr2v;
- debug_printf( DEBUG_ARITH, L"); lcm = %ld; m1 = %ld; m2 = %ld", lcm, m1, m2 );
+ debug_printf( DEBUG_ARITH, L"); lcm = %ld; m1 = %ld; m2 = %ld", lcm,
+ m1, m2 );
if ( dr1v == dr2v ) {
- r = make_ratio( frame_pointer,
- make_integer( dd1v + dd2v ),
+ r = make_ratio( make_integer( dd1v + dd2v, NIL ),
cell1.payload.ratio.divisor );
} else {
- struct cons_pointer dd1vm = make_integer( dd1v * m1 ),
- dr1vm = make_integer( dr1v * m1 ),
- dd2vm = make_integer( dd2v * m2 ),
- dr2vm = make_integer( dr2v * m2 ),
- r1 = make_ratio( frame_pointer, dd1vm, dr1vm ),
- r2 = make_ratio( frame_pointer, dd2vm, dr2vm );
+ struct cons_pointer dd1vm = make_integer( dd1v * m1, NIL ),
+ dr1vm = make_integer( dr1v * m1, NIL ),
+ dd2vm = make_integer( dd2v * m2, NIL ),
+ dr2vm = make_integer( dr2v * m2, NIL ),
+ r1 = make_ratio( dd1vm, dr1vm ),
+ r2 = make_ratio( dd2vm, dr2vm );
- r = add_ratio_ratio( frame_pointer, r1, r2 );
+ r = add_ratio_ratio( r1, r2 );
/* because the references on dd1vm, dr1vm, dd2vm and dr2vm were
* never incremented except when making r1 and r2, decrementing
@@ -140,7 +129,7 @@ struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
dec_ref( r2 );
}
- result = simplify_ratio( frame_pointer, r );
+ result = simplify_ratio( r );
if ( !eq( r, result ) ) {
dec_ref( r );
}
@@ -150,7 +139,7 @@ struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
( L"Shouldn't happen: bad arg to add_ratio_ratio" ),
make_cons( arg1,
make_cons( arg2, NIL ) ) ),
- frame_pointer );
+ NIL );
}
debug_print( L" => ", DEBUG_ARITH );
@@ -164,18 +153,19 @@ struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
/**
* return a cons_pointer indicating a number which is the sum of
* the intger indicated by `intarg` and the ratio indicated by
- * `ratarg`. If you pass other types, this is going to break horribly.
+ * `ratarg`.
+ * @exception if either `intarg` or `ratarg` is not of the expected type.
*/
-struct cons_pointer add_integer_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer intarg,
+struct cons_pointer add_integer_ratio( struct cons_pointer intarg,
struct cons_pointer ratarg ) {
struct cons_pointer result;
if ( integerp( intarg ) && ratiop( ratarg ) ) {
- struct cons_pointer one = make_integer( 1 ),
- ratio = make_ratio( frame_pointer, intarg, one );
+ // TODO: not longer works
+ struct cons_pointer one = make_integer( 1, NIL ),
+ ratio = make_ratio( intarg, one );
- result = add_ratio_ratio( frame_pointer, ratio, ratarg );
+ result = add_ratio_ratio( ratio, ratarg );
dec_ref( one );
dec_ref( ratio );
@@ -185,8 +175,7 @@ struct cons_pointer add_integer_ratio( struct cons_pointer frame_pointer,
( L"Shouldn't happen: bad arg to add_integer_ratio" ),
make_cons( intarg,
make_cons( ratarg,
- NIL ) ) ),
- frame_pointer );
+ NIL ) ) ), NIL );
}
return result;
@@ -194,19 +183,17 @@ struct cons_pointer add_integer_ratio( struct cons_pointer frame_pointer,
/**
* return a cons_pointer to a ratio which represents the value of the ratio
- * indicated by `arg1` divided by the ratio indicated by `arg2`. If either
- * of these aren't RTIO cells, something horrid will happen and it is YOUR FAULT.
+ * indicated by `arg1` divided by the ratio indicated by `arg2`.
+ * @exception will return an exception if either `arg1` or `arg2` is not a
+ * rational number.
*/
-struct cons_pointer divide_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer divide_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 ) {
- struct cons_pointer i = make_ratio( frame_pointer,
- pointer2cell( arg2 ).payload.
- ratio.divisor,
- pointer2cell( arg2 ).payload.
- ratio.dividend ),
- result =
- multiply_ratio_ratio( frame_pointer, arg1, i );
+ // TODO: this now has to work if `arg1` is an integer
+ struct cons_pointer i =
+ make_ratio( pointer2cell( arg2 ).payload.ratio.divisor,
+ pointer2cell( arg2 ).payload.ratio.dividend ), result =
+ multiply_ratio_ratio( arg1, i );
dec_ref( i );
@@ -215,12 +202,14 @@ struct cons_pointer divide_ratio_ratio( struct cons_pointer frame_pointer,
/**
* return a cons_pointer indicating a number which is the product of
- * the ratios indicated by `arg1` and `arg2`. If you pass non-ratios,
- * this is going to break horribly.
+ * the ratios indicated by `arg1` and `arg2`.
+ * @exception will return an exception if either `arg1` or `arg2` is not a
+ * rational number.
*/
-struct cons_pointer multiply_ratio_ratio( struct cons_pointer frame_pointer, struct
+struct cons_pointer multiply_ratio_ratio( struct
cons_pointer arg1, struct
cons_pointer arg2 ) {
+ // TODO: this now has to work if arg1 is an integer
struct cons_pointer result;
debug_print( L"multiply_ratio_ratio( arg1 = ", DEBUG_ARITH );
@@ -243,9 +232,9 @@ struct cons_pointer multiply_ratio_ratio( struct cons_pointer frame_pointer, str
ddrv = dd1v * dd2v, drrv = dr1v * dr2v;
struct cons_pointer unsimplified =
- make_ratio( frame_pointer, make_integer( ddrv ),
- make_integer( drrv ) );
- result = simplify_ratio( frame_pointer, unsimplified );
+ make_ratio( make_integer( ddrv, NIL ),
+ make_integer( drrv, NIL ) );
+ result = simplify_ratio( unsimplified );
if ( !eq( unsimplified, result ) ) {
dec_ref( unsimplified );
@@ -254,7 +243,7 @@ struct cons_pointer multiply_ratio_ratio( struct cons_pointer frame_pointer, str
result =
throw_exception( c_string_to_lisp_string
( L"Shouldn't happen: bad arg to multiply_ratio_ratio" ),
- frame_pointer );
+ NIL );
}
return result;
@@ -263,17 +252,18 @@ struct cons_pointer multiply_ratio_ratio( struct cons_pointer frame_pointer, str
/**
* return a cons_pointer indicating a number which is the product of
* the intger indicated by `intarg` and the ratio indicated by
- * `ratarg`. If you pass other types, this is going to break horribly.
+ * `ratarg`.
+ * @exception if either `intarg` or `ratarg` is not of the expected type.
*/
-struct cons_pointer multiply_integer_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer intarg,
+struct cons_pointer multiply_integer_ratio( struct cons_pointer intarg,
struct cons_pointer ratarg ) {
struct cons_pointer result;
if ( integerp( intarg ) && ratiop( ratarg ) ) {
- struct cons_pointer one = make_integer( 1 ),
- ratio = make_ratio( frame_pointer, intarg, one );
- result = multiply_ratio_ratio( frame_pointer, ratio, ratarg );
+ // TODO: no longer works; fix
+ struct cons_pointer one = make_integer( 1, NIL ),
+ ratio = make_ratio( intarg, one );
+ result = multiply_ratio_ratio( ratio, ratarg );
dec_ref( one );
dec_ref( ratio );
@@ -281,7 +271,7 @@ struct cons_pointer multiply_integer_ratio( struct cons_pointer frame_pointer,
result =
throw_exception( c_string_to_lisp_string
( L"Shouldn't happen: bad arg to multiply_integer_ratio" ),
- frame_pointer );
+ NIL );
}
return result;
@@ -290,14 +280,14 @@ struct cons_pointer multiply_integer_ratio( struct cons_pointer frame_pointer,
/**
* return a cons_pointer indicating a number which is the difference of
- * the ratios indicated by `arg1` and `arg2`. If you pass non-ratios,
- * this is going to break horribly.
+ * the ratios indicated by `arg1` and `arg2`.
+ * @exception will return an exception if either `arg1` or `arg2` is not a
+ * rational number.
*/
-struct cons_pointer subtract_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer subtract_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 ) {
- struct cons_pointer i = inverse( frame_pointer, arg2 ),
- result = add_ratio_ratio( frame_pointer, arg1, i );
+ struct cons_pointer i = negative( arg2 ),
+ result = add_ratio_ratio( arg1, i );
dec_ref( i );
@@ -306,17 +296,18 @@ struct cons_pointer subtract_ratio_ratio( struct cons_pointer frame_pointer,
/**
- * Construct a ratio frame from these two pointers, expected to be integers
- * or (later) bignums, in the context of this stack_frame.
+ * Construct a ratio frame from this `dividend` and `divisor`, expected to
+ * be integers, in the context of the stack_frame indicated by this
+ * `frame_pointer`.
+ * @exception if either `dividend` or `divisor` is not an integer.
*/
-struct cons_pointer make_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer dividend,
+struct cons_pointer make_ratio( struct cons_pointer dividend,
struct cons_pointer divisor ) {
struct cons_pointer result;
if ( integerp( dividend ) && integerp( divisor ) ) {
inc_ref( dividend );
inc_ref( divisor );
- result = allocate_cell( RATIOTAG );
+ result = allocate_cell( RATIOTV );
struct cons_space_object *cell = &pointer2cell( result );
cell->payload.ratio.dividend = dividend;
cell->payload.ratio.divisor = divisor;
@@ -324,10 +315,28 @@ struct cons_pointer make_ratio( struct cons_pointer frame_pointer,
result =
throw_exception( c_string_to_lisp_string
( L"Dividend and divisor of a ratio must be integers" ),
- frame_pointer );
+ NIL );
}
debug_dump_object( result, DEBUG_ARITH );
+ return result;
+}
+
+/**
+ * True if a and be are identical ratios, else false.
+ */
+bool equal_ratio_ratio( struct cons_pointer a, struct cons_pointer b ) {
+ bool result = false;
+
+ if ( ratiop( a ) && ratiop( b ) ) {
+ struct cons_space_object *cell_a = &pointer2cell( a );
+ struct cons_space_object *cell_b = &pointer2cell( b );
+
+ result = equal_integer_integer( cell_a->payload.ratio.dividend,
+ cell_b->payload.ratio.dividend ) &&
+ equal_integer_integer( cell_a->payload.ratio.divisor,
+ cell_b->payload.ratio.divisor );
+ }
return result;
}
diff --git a/src/arith/ratio.h b/src/arith/ratio.h
index 5a3b0d6..9068bfb 100644
--- a/src/arith/ratio.h
+++ b/src/arith/ratio.h
@@ -11,36 +11,29 @@
#ifndef __ratio_h
#define __ratio_h
-struct cons_pointer simplify_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg );
+struct cons_pointer simplify_ratio( struct cons_pointer arg );
-struct cons_pointer add_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer add_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 );
-struct cons_pointer add_integer_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer intarg,
+struct cons_pointer add_integer_ratio( struct cons_pointer intarg,
struct cons_pointer ratarg );
-struct cons_pointer divide_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer divide_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 );
-struct cons_pointer multiply_ratio_ratio( struct cons_pointer frame_pointer, struct
- cons_pointer arg1, struct
+struct cons_pointer multiply_ratio_ratio( struct cons_pointer arg1, struct
cons_pointer arg2 );
-struct cons_pointer multiply_integer_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer intarg,
+struct cons_pointer multiply_integer_ratio( struct cons_pointer intarg,
struct cons_pointer ratarg );
-struct cons_pointer subtract_ratio_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer arg1,
+struct cons_pointer subtract_ratio_ratio( struct cons_pointer arg1,
struct cons_pointer arg2 );
-struct cons_pointer make_ratio( struct cons_pointer frame_pointer,
- struct cons_pointer dividend,
+struct cons_pointer make_ratio( struct cons_pointer dividend,
struct cons_pointer divisor );
+bool equal_ratio_ratio( struct cons_pointer a, struct cons_pointer b );
#endif
diff --git a/src/arith/real.c b/src/arith/real.c
index 84ba899..34d29d0 100644
--- a/src/arith/real.c
+++ b/src/arith/real.c
@@ -7,10 +7,10 @@
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
-#include "conspage.h"
-#include "consspaceobject.h"
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
#include "debug.h"
-#include "read.h"
+#include "io/read.h"
/**
* Allocate a real number cell representing this value and return a cons
@@ -19,7 +19,7 @@
* @return a real number cell wrapping this value.
*/
struct cons_pointer make_real( long double value ) {
- struct cons_pointer result = allocate_cell( REALTAG );
+ struct cons_pointer result = allocate_cell( REALTV );
struct cons_space_object *cell = &pointer2cell( result );
cell->payload.real.value = value;
diff --git a/src/authorise.c b/src/authorise.c
new file mode 100644
index 0000000..afd730d
--- /dev/null
+++ b/src/authorise.c
@@ -0,0 +1,24 @@
+/*
+ * authorised.c
+ *
+ * For now, a dummy authorising everything.
+ *
+ * (c) 2021 Simon Brooke
+ * Licensed under GPL version 2.0, or, at your option, any later version.
+ */
+
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
+
+
+/**
+ * TODO: does nothing, yet. What it should do is access a magic value in the
+ * runtime environment and check that it is identical to something on this `acl`
+ */
+struct cons_pointer authorised( struct cons_pointer target,
+ struct cons_pointer acl ) {
+ if ( nilp( acl ) ) {
+ acl = pointer2cell( target ).access;
+ }
+ return TRUE;
+}
diff --git a/src/authorise.h b/src/authorise.h
new file mode 100644
index 0000000..6c55b32
--- /dev/null
+++ b/src/authorise.h
@@ -0,0 +1,16 @@
+/*
+ * authorise.h
+ *
+ * Basic implementation of a authorisation.
+ *
+ * (c) 2021 Simon Brooke
+ * Licensed under GPL version 2.0, or, at your option, any later version.
+ */
+
+#ifndef __psse_authorise_h
+#define __psse_authorise_h
+
+struct cons_pointer authorised( struct cons_pointer target,
+ struct cons_pointer acl );
+
+#endif
diff --git a/src/debug.c b/src/debug.c
index b21f4af..233e154 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -18,10 +18,11 @@
#include
#include
-#include "consspaceobject.h"
+#include "memory/consspaceobject.h"
#include "debug.h"
-#include "dump.h"
-#include "print.h"
+#include "memory/dump.h"
+#include "io/io.h"
+#include "io/print.h"
/**
* the controlling flags for `debug_print`; set in `init.c`, q.v.
@@ -42,6 +43,30 @@ void debug_print( wchar_t *message, int level ) {
#endif
}
+/**
+ * stolen from https://stackoverflow.com/questions/11656241/how-to-print-uint128-t-number-using-gcc
+ */
+void debug_print_128bit( __int128_t n, int level ) {
+#ifdef DEBUG
+ if ( level & verbosity ) {
+ if ( n == 0 ) {
+ fwprintf( stderr, L"0" );
+ } else {
+ char str[40] = { 0 }; // log10(1 << 128) + '\0'
+ char *s = str + sizeof( str ) - 1; // start at the end
+ while ( n != 0 ) {
+ if ( s == str )
+ return; // never happens
+
+ *--s = "0123456789"[n % 10]; // save last digit
+ n /= 10; // drop it
+ }
+ fwprintf( stderr, L"%s", s );
+ }
+ }
+#endif
+}
+
/**
* print a line feed to stderr, if `verbosity` matches `level`.
* `verbosity is a set of flags, see debug_print.h; so you can
@@ -61,15 +86,15 @@ void debug_println( int level ) {
* `wprintf` adapted for the debug logging system. Print to stderr only
* `verbosity` matches `level`. All other arguments as for `wprintf`.
*/
-void debug_printf( int level, wchar_t * format, ...) {
- #ifdef DEBUG
- if ( level & verbosity ) {
- fwide( stderr, 1 );
- va_list(args);
- va_start(args, format);
- vfwprintf(stderr, format, args);
- }
- #endif
+void debug_printf( int level, wchar_t *format, ... ) {
+#ifdef DEBUG
+ if ( level & verbosity ) {
+ fwide( stderr, 1 );
+ va_list( args );
+ va_start( args, format );
+ vfwprintf( stderr, format, args );
+ }
+#endif
}
/**
@@ -80,8 +105,10 @@ void debug_printf( int level, wchar_t * format, ...) {
void debug_print_object( struct cons_pointer pointer, int level ) {
#ifdef DEBUG
if ( level & verbosity ) {
+ URL_FILE *ustderr = file_to_url_file( stderr );
fwide( stderr, 1 );
- print( stderr, pointer );
+ print( ustderr, pointer );
+ free( ustderr );
}
#endif
}
@@ -92,8 +119,10 @@ void debug_print_object( struct cons_pointer pointer, int level ) {
void debug_dump_object( struct cons_pointer pointer, int level ) {
#ifdef DEBUG
if ( level & verbosity ) {
- fwide( stderr, 1 );
- dump_object( stderr, pointer );
+ URL_FILE *ustderr = file_to_url_file( stderr );
+ fwide( stderr, 1 );
+ dump_object( ustderr, pointer );
+ free( ustderr );
}
#endif
}
diff --git a/src/debug.h b/src/debug.h
index 22f5591..babbaea 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -14,19 +14,21 @@
#define __debug_print_h
#define DEBUG_ALLOC 1
-#define DEBUG_STACK 2
-#define DEBUG_ARITH 4
-#define DEBUG_EVAL 8
-#define DEBUG_LAMBDA 16
-#define DEBUG_BOOTSTRAP 32
-#define DEBUG_IO 64
+#define DEBUG_ARITH 2
+#define DEBUG_BIND 4
+#define DEBUG_BOOTSTRAP 8
+#define DEBUG_EVAL 16
+#define DEBUG_IO 32
+#define DEBUG_LAMBDA 64
#define DEBUG_REPL 128
+#define DEBUG_STACK 256
extern int verbosity;
void debug_print( wchar_t *message, int level );
+void debug_print_128bit( __int128_t n, int level );
void debug_println( int level );
-void debug_printf( int level, wchar_t * format, ...);
+void debug_printf( int level, wchar_t *format, ... );
void debug_print_object( struct cons_pointer pointer, int level );
void debug_dump_object( struct cons_pointer pointer, int level );
diff --git a/src/init.c b/src/init.c
index d81aa00..676964f 100644
--- a/src/init.c
+++ b/src/init.c
@@ -9,57 +9,138 @@
* Licensed under GPL version 2.0, or, at your option, any later version.
*/
+#include
#include
#include
#include
#include
#include
+/* libcurl, used for io */
+#include
+
#include "version.h"
-#include "conspage.h"
-#include "consspaceobject.h"
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
+#include "memory/stack.h"
#include "debug.h"
-#include "intern.h"
-#include "lispops.h"
-#include "peano.h"
-#include "print.h"
+#include "memory/hashmap.h"
+#include "ops/intern.h"
+#include "io/io.h"
+#include "ops/lispops.h"
+#include "ops/meta.h"
+#include "arith/peano.h"
+#include "io/print.h"
#include "repl.h"
+#include "io/fopen.h"
+#include "time/psse_time.h"
// extern char *optarg; /* defined in unistd.h */
+/**
+ * Bind this compiled `executable` function, as a Lisp function, to
+ * this name in the `oblist`.
+ * \todo where a function is not compiled from source, we could cache
+ * the name on the source pointer. Would make stack frames potentially
+ * more readable and aid debugging generally.
+ */
void bind_function( wchar_t *name, struct cons_pointer ( *executable )
( struct stack_frame *,
struct cons_pointer, struct cons_pointer ) ) {
- deep_bind( c_string_to_lisp_symbol( name ),
- make_function( NIL, executable ) );
+ struct cons_pointer n = c_string_to_lisp_symbol( name );
+ struct cons_pointer meta =
+ make_cons( make_cons( c_string_to_lisp_keyword( L"primitive" ), TRUE ),
+ make_cons( make_cons( c_string_to_lisp_keyword( L"name" ),
+ n ),
+ NIL ) );
+
+ deep_bind( n, make_function( meta, executable ) );
}
+/**
+ * Bind this compiled `executable` function, as a Lisp special form, to
+ * this `name` in the `oblist`.
+ */
void bind_special( wchar_t *name, struct cons_pointer ( *executable )
( struct stack_frame *,
struct cons_pointer, struct cons_pointer ) ) {
- deep_bind( c_string_to_lisp_symbol( name ),
- make_special( NIL, executable ) );
+ struct cons_pointer n = c_string_to_lisp_symbol( name );
+ struct cons_pointer meta =
+ make_cons( make_cons( c_string_to_lisp_keyword( L"primitive" ), TRUE ),
+ make_cons( make_cons( c_string_to_lisp_keyword( L"name" ),
+ n ),
+ NIL ) );
+
+ deep_bind( n, make_special( meta, executable ) );
}
+/**
+ * Bind this `value` to this `name` in the `oblist`.
+ */
+void bind_value( wchar_t *name, struct cons_pointer value ) {
+ struct cons_pointer n = c_string_to_lisp_symbol( name );
+ inc_ref( n );
+
+ deep_bind( n, value );
+
+ dec_ref( n );
+}
+
+void print_banner( ) {
+ fwprintf( stdout, L"Post-Scarcity Software Environment version %s\n\n",
+ VERSION );
+}
+
+/**
+ * Print command line options to this `stream`.
+ *
+ * @stream the stream to print to.
+ */
+void print_options( FILE * stream ) {
+ fwprintf( stream, L"Expected options are:\n" );
+ fwprintf( stream,
+ L"\t-d\tDump memory to standard out at end of run (copious!);\n" );
+ fwprintf( stream, L"\t-h\tPrint this message and exit;\n" );
+ fwprintf( stream, L"\t-p\tShow a prompt (default is no prompt);\n" );
+ fwprintf( stream,
+ L"\t-v LEVEL\n\t\tSet verbosity to the specified level (0...512)\n" );
+ fwprintf( stream, L"\t\tWhere bits are interpreted as follows:\n" );
+ fwprintf( stream, L"\t\t1\tALLOC;\n" );
+ fwprintf( stream, L"\t\t2\tARITH;\n" );
+ fwprintf( stream, L"\t\t4\tBIND;\n" );
+ fwprintf( stream, L"\t\t8\tBOOTSTRAP;\n" );
+ fwprintf( stream, L"\t\t16\tEVAL;\n" );
+ fwprintf( stream, L"\t\t32\tINPUT/OUTPUT;\n" );
+ fwprintf( stream, L"\t\t64\tLAMBDA;\n" );
+ fwprintf( stream, L"\t\t128\tREPL;\n" );
+ fwprintf( stream, L"\t\t256\tSTACK.\n" );
+}
+
+/**
+ * main entry point; parse command line arguments, initialise the environment,
+ * and enter the read-eval-print loop.
+ */
int main( int argc, char *argv[] ) {
- /*
- * attempt to set wide character acceptance on all streams
- */
- fwide( stdin, 1 );
- fwide( stdout, 1 );
- fwide( stderr, 1 );
int option;
bool dump_at_end = false;
bool show_prompt = false;
- while ( ( option = getopt( argc, argv, "cpdv:" ) ) != -1 ) {
+ setlocale( LC_ALL, "" );
+ if ( io_init( ) != 0 ) {
+ fputs( "Failed to initialise I/O subsystem\n", stderr );
+ exit( 1 );
+ }
+
+ while ( ( option = getopt( argc, argv, "phdv:" ) ) != -1 ) {
switch ( option ) {
- case 'c':
- print_use_colours = true;
- break;
case 'd':
dump_at_end = true;
break;
+ case 'h':
+ print_banner( );
+ print_options( stdout );
+ exit( 0 );
+ break;
case 'p':
show_prompt = true;
break;
@@ -68,14 +149,14 @@ int main( int argc, char *argv[] ) {
break;
default:
fwprintf( stderr, L"Unexpected option %c\n", option );
+ print_options( stderr );
+ exit( 1 );
break;
}
}
if ( show_prompt ) {
- fwprintf( stdout,
- L"Post scarcity software environment version %s\n\n",
- VERSION );
+ print_banner( );
}
debug_print( L"About to initialise cons pages\n", DEBUG_BOOTSTRAP );
@@ -84,60 +165,132 @@ int main( int argc, char *argv[] ) {
debug_print( L"Initialised cons pages, about to bind\n", DEBUG_BOOTSTRAP );
+// TODO: oblist-as-hashmap (which is what we ultimately need) is failing hooribly
+// oblist = inc_ref( make_hashmap( 32, NIL, TRUE ) );
+
/*
* privileged variables (keywords)
*/
- deep_bind( c_string_to_lisp_symbol( L"nil" ), NIL );
- deep_bind( c_string_to_lisp_symbol( L"t" ), TRUE );
+ bind_value( L"nil", NIL );
+ bind_value( L"t", TRUE );
+ /*
+ * standard input, output, error and sink streams
+ * attempt to set wide character acceptance on all streams
+ */
+ URL_FILE *sink = url_fopen( "/dev/null", "w" );
+ fwide( stdin, 1 );
+ fwide( stdout, 1 );
+ fwide( stderr, 1 );
+ fwide( sink->handle.file, 1 );
+ bind_value( L"*in*", make_read_stream( file_to_url_file( stdin ),
+ make_cons( make_cons
+ ( c_string_to_lisp_keyword
+ ( L"url" ),
+ c_string_to_lisp_string
+ ( L"system:standard input" ) ),
+ NIL ) ) );
+ bind_value( L"*out*",
+ make_write_stream( file_to_url_file( stdout ),
+ make_cons( make_cons
+ ( c_string_to_lisp_keyword
+ ( L"url" ),
+ c_string_to_lisp_string
+ ( L"system:standard output]" ) ),
+ NIL ) ) );
+ bind_value( L"*log*", make_write_stream( file_to_url_file( stderr ),
+ make_cons( make_cons
+ ( c_string_to_lisp_keyword
+ ( L"url" ),
+ c_string_to_lisp_string
+ ( L"system:standard log" ) ),
+ NIL ) ) );
+ bind_value( L"*sink*", make_write_stream( sink,
+ make_cons( make_cons
+ ( c_string_to_lisp_keyword
+ ( L"url" ),
+ c_string_to_lisp_string
+ ( L"system:standard sink" ) ),
+ NIL ) ) );
+ /*
+ * the default prompt
+ */
+ bind_value( L"*prompt*",
+ show_prompt ? c_string_to_lisp_symbol( L":: " ) : NIL );
/*
* primitive function operations
*/
- bind_function( L"add", &lisp_add );
- bind_function( L"apply", &lisp_apply );
- bind_function( L"assoc", &lisp_assoc );
- bind_function( L"car", &lisp_car );
- bind_function( L"cdr", &lisp_cdr );
- bind_function( L"cons", &lisp_cons );
- bind_function( L"divide", &lisp_divide );
- bind_function( L"eq", &lisp_eq );
- bind_function( L"equal", &lisp_equal );
- bind_function( L"eval", &lisp_eval );
- bind_function( L"exception", &lisp_exception );
- bind_function( L"multiply", &lisp_multiply );
- bind_function( L"read", &lisp_read );
- bind_function( L"oblist", &lisp_oblist );
- bind_function( L"print", &lisp_print );
- bind_function( L"progn", &lisp_progn );
- bind_function( L"reverse", &lisp_reverse );
- bind_function( L"set", &lisp_set );
- bind_function( L"subtract", &lisp_subtract );
- bind_function( L"throw", &lisp_exception );
- bind_function( L"type", &lisp_type );
-
- bind_function( L"+", &lisp_add );
- bind_function( L"*", &lisp_multiply );
- bind_function( L"-", &lisp_subtract );
- bind_function( L"/", &lisp_divide );
- bind_function( L"=", &lisp_equal );
-
+ bind_function( L"absolute", &lisp_absolute );
+ bind_function( L"add", &lisp_add );
+ bind_function( L"append", &lisp_append );
+ bind_function( L"apply", &lisp_apply );
+ bind_function( L"assoc", &lisp_assoc );
+ bind_function( L"car", &lisp_car );
+ bind_function( L"cdr", &lisp_cdr );
+ bind_function( L"close", &lisp_close );
+ bind_function( L"cons", &lisp_cons );
+ bind_function( L"divide", &lisp_divide );
+ bind_function( L"eq", &lisp_eq );
+ bind_function( L"equal", &lisp_equal );
+ bind_function( L"eval", &lisp_eval );
+ bind_function( L"exception", &lisp_exception );
+ bind_function( L"get-hash", &lisp_get_hash );
+ bind_function( L"hashmap", lisp_make_hashmap );
+ bind_function( L"inspect", &lisp_inspect );
+ bind_function( L"keys", &lisp_keys );
+ bind_function( L"list", &lisp_list );
+ bind_function( L"mapcar", &lisp_mapcar );
+ bind_function( L"meta", &lisp_metadata );
+ bind_function( L"metadata", &lisp_metadata );
+ bind_function( L"multiply", &lisp_multiply );
+ bind_function( L"negative?", &lisp_is_negative );
+ bind_function( L"oblist", &lisp_oblist );
+ bind_function( L"open", &lisp_open );
+ bind_function( L"print", &lisp_print );
+ bind_function( L"put!", lisp_hashmap_put );
+ bind_function( L"put-all!", &lisp_hashmap_put_all );
+ bind_function( L"read", &lisp_read );
+ bind_function( L"read-char", &lisp_read_char );
+ bind_function( L"repl", &lisp_repl );
+ bind_function( L"reverse", &lisp_reverse );
+ bind_function( L"set", &lisp_set );
+ bind_function( L"slurp", &lisp_slurp );
+ bind_function( L"source", &lisp_source );
+ bind_function( L"subtract", &lisp_subtract );
+ bind_function( L"throw", &lisp_exception );
+ bind_function( L"time", &lisp_time );
+ bind_function( L"type", &lisp_type );
+ bind_function( L"+", &lisp_add );
+ bind_function( L"*", &lisp_multiply );
+ bind_function( L"-", &lisp_subtract );
+ bind_function( L"/", &lisp_divide );
+ bind_function( L"=", &lisp_equal );
/*
* primitive special forms
*/
- bind_special( L"cond", &lisp_cond );
- bind_special( L"lambda", &lisp_lambda );
- // bind_special( L"λ", &lisp_lambda );
- bind_special( L"nlambda", &lisp_nlambda );
- // bind_special( L"nλ", &lisp_nlambda );
- bind_special( L"progn", &lisp_progn );
- bind_special( L"quote", &lisp_quote );
- bind_special( L"set!", &lisp_set_shriek );
+ bind_special( L"cond", &lisp_cond );
+ bind_special( L"lambda", &lisp_lambda );
+ bind_special( L"\u03bb", &lisp_lambda ); // λ
+ bind_special( L"let", &lisp_let );
+ bind_special( L"nlambda", &lisp_nlambda );
+ bind_special( L"n\u03bb", &lisp_nlambda );
+ bind_special( L"progn", &lisp_progn );
+ bind_special( L"quote", &lisp_quote );
+ bind_special( L"set!", &lisp_set_shriek );
+ bind_special( L"try", &lisp_try );
+ debug_print( L"Initialised oblist\n", DEBUG_BOOTSTRAP );
+ debug_dump_object( oblist, DEBUG_BOOTSTRAP );
- repl( stdin, stdout, stderr, show_prompt );
+ repl( show_prompt );
+ debug_print( L"Freeing oblist\n", DEBUG_BOOTSTRAP );
+ dec_ref( oblist );
+ debug_dump_object( oblist, DEBUG_BOOTSTRAP );
if ( dump_at_end ) {
- dump_pages( stdout );
+ dump_pages( file_to_url_file( stdout ) );
}
+ summarise_allocation( );
+ curl_global_cleanup( );
return ( 0 );
}
diff --git a/src/io/fopen.c b/src/io/fopen.c
new file mode 100644
index 0000000..e4fafdd
--- /dev/null
+++ b/src/io/fopen.c
@@ -0,0 +1,526 @@
+/*
+ * fopen.c
+ *
+ * adapted from https://curl.haxx.se/libcurl/c/fopen.html.
+ *
+ * Modifications to read/write wide character streams by
+ * Simon Brooke.
+ *
+ * NOTE THAT: for my purposes, I'm only interested in wide characters,
+ * and I always read them one character at a time.
+ *
+ * Copyright (c) 2003, 2017 Simtec Electronics
+ * Some portions (c) 2019 Simon Brooke
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This example requires libcurl 7.9.7 or later.
+ */
+
+#include
+#include
+#ifndef WIN32
+#include
+#endif
+#include
+#include
+
+#include
+
+#include "io/fopen.h"
+#ifdef FOPEN_STANDALONE
+CURLSH *io_share;
+#else
+#include "memory/consspaceobject.h"
+#include "io/io.h"
+#include "utils.h"
+#endif
+
+
+/* exported functions */
+URL_FILE *url_fopen( const char *url, const char *operation );
+int url_fclose( URL_FILE * file );
+int url_feof( URL_FILE * file );
+size_t url_fread( void *ptr, size_t size, size_t nmemb, URL_FILE * file );
+char *url_fgets( char *ptr, size_t size, URL_FILE * file );
+void url_rewind( URL_FILE * file );
+
+/* we use a global one for convenience */
+static CURLM *multi_handle;
+
+/* curl calls this routine to get more data */
+static size_t write_callback( char *buffer,
+ size_t size, size_t nitems, void *userp ) {
+ char *newbuff;
+ size_t rembuff;
+
+ URL_FILE *url = ( URL_FILE * ) userp;
+ size *= nitems;
+
+ rembuff = url->buffer_len - url->buffer_pos; /* remaining space in buffer */
+
+ if ( size > rembuff ) {
+ /* not enough space in buffer */
+ newbuff = realloc( url->buffer, url->buffer_len + ( size - rembuff ) );
+ if ( newbuff == NULL ) {
+ fprintf( stderr, "callback buffer grow failed\n" );
+ size = rembuff;
+ } else {
+ /* realloc succeeded increase buffer size */
+ url->buffer_len += size - rembuff;
+ url->buffer = newbuff;
+ }
+ }
+
+ memcpy( &url->buffer[url->buffer_pos], buffer, size );
+ url->buffer_pos += size;
+
+ return size;
+}
+
+/* use to attempt to fill the read buffer up to requested number of bytes */
+static int fill_buffer( URL_FILE * file, size_t want ) {
+ fd_set fdread;
+ fd_set fdwrite;
+ fd_set fdexcep;
+ struct timeval timeout;
+ int rc;
+ CURLMcode mc; /* curl_multi_fdset() return code */
+
+ /* only attempt to fill buffer if transactions still running and buffer
+ * doesn't exceed required size already
+ */
+ if ( ( !file->still_running ) || ( file->buffer_pos > want ) )
+ return 0;
+
+ /* attempt to fill buffer */
+ do {
+ int maxfd = -1;
+ long curl_timeo = -1;
+
+ FD_ZERO( &fdread );
+ FD_ZERO( &fdwrite );
+ FD_ZERO( &fdexcep );
+
+ /* set a suitable timeout to fail on */
+ timeout.tv_sec = 60; /* 1 minute */
+ timeout.tv_usec = 0;
+
+ curl_multi_timeout( multi_handle, &curl_timeo );
+ if ( curl_timeo >= 0 ) {
+ timeout.tv_sec = curl_timeo / 1000;
+ if ( timeout.tv_sec > 1 )
+ timeout.tv_sec = 1;
+ else
+ timeout.tv_usec = ( curl_timeo % 1000 ) * 1000;
+ }
+
+ /* get file descriptors from the transfers */
+ mc = curl_multi_fdset( multi_handle, &fdread, &fdwrite, &fdexcep,
+ &maxfd );
+
+ if ( mc != CURLM_OK ) {
+ fprintf( stderr, "curl_multi_fdset() failed, code %d.\n", mc );
+ break;
+ }
+
+ /* On success the value of maxfd is guaranteed to be >= -1. We call
+ select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
+ no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
+ to sleep 100ms, which is the minimum suggested value in the
+ curl_multi_fdset() doc. */
+
+ if ( maxfd == -1 ) {
+#ifdef _WIN32
+ Sleep( 100 );
+ rc = 0;
+#else
+ /* Portable sleep for platforms other than Windows. */
+ struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
+ rc = select( 0, NULL, NULL, NULL, &wait );
+#endif
+ } else {
+ /* Note that on some platforms 'timeout' may be modified by select().
+ If you need access to the original value save a copy beforehand. */
+ rc = select( maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout );
+ }
+
+ switch ( rc ) {
+ case -1:
+ /* select error */
+ break;
+
+ case 0:
+ default:
+ /* timeout or readable/writable sockets */
+ curl_multi_perform( multi_handle, &file->still_running );
+ break;
+ }
+ } while ( file->still_running && ( file->buffer_pos < want ) );
+ return 1;
+}
+
+/* use to remove want bytes from the front of a files buffer */
+static int use_buffer( URL_FILE * file, size_t want ) {
+ /* sort out buffer */
+ if ( ( file->buffer_pos - want ) <= 0 ) {
+ /* ditch buffer - write will recreate */
+ free( file->buffer );
+ file->buffer = NULL;
+ file->buffer_pos = 0;
+ file->buffer_len = 0;
+ } else {
+ /* move rest down make it available for later */
+ memmove( file->buffer,
+ &file->buffer[want], ( file->buffer_pos - want ) );
+
+ file->buffer_pos -= want;
+ }
+ return 0;
+}
+
+URL_FILE *url_fopen( const char *url, const char *operation ) {
+ /* this code could check for URLs or types in the 'url' and
+ basically use the real fopen() for standard files */
+
+ URL_FILE *file;
+ ( void ) operation;
+
+ file = calloc( 1, sizeof( URL_FILE ) );
+ if ( !file )
+ return NULL;
+
+ file->handle.file = fopen( url, operation );
+ if ( file->handle.file ) {
+ file->type = CFTYPE_FILE; /* marked as file */
+ } else if ( index_of( ':', url ) > -1 ) {
+ file->type = CFTYPE_CURL; /* marked as URL */
+ file->handle.curl = curl_easy_init( );
+
+ curl_easy_setopt( file->handle.curl, CURLOPT_URL, url );
+ curl_easy_setopt( file->handle.curl, CURLOPT_WRITEDATA, file );
+ curl_easy_setopt( file->handle.curl, CURLOPT_VERBOSE, 0L );
+ curl_easy_setopt( file->handle.curl, CURLOPT_WRITEFUNCTION,
+ write_callback );
+ /* use the share object */
+ curl_easy_setopt( file->handle.curl, CURLOPT_SHARE, io_share );
+
+
+ if ( !multi_handle )
+ multi_handle = curl_multi_init( );
+
+ curl_multi_add_handle( multi_handle, file->handle.curl );
+
+ /* lets start the fetch */
+ curl_multi_perform( multi_handle, &file->still_running );
+
+ if ( ( file->buffer_pos == 0 ) && ( !file->still_running ) ) {
+ /* if still_running is 0 now, we should return NULL */
+
+ /* make sure the easy handle is not in the multi handle anymore */
+ curl_multi_remove_handle( multi_handle, file->handle.curl );
+
+ /* cleanup */
+ curl_easy_cleanup( file->handle.curl );
+
+ free( file );
+
+ file = NULL;
+ }
+ } else {
+ file->type = CFTYPE_NONE;
+ /* not a file, and doesn't look like a URL. */
+ }
+
+ return file;
+}
+
+int url_fclose( URL_FILE * file ) {
+ int ret = 0; /* default is good return */
+
+ switch ( file->type ) {
+ case CFTYPE_FILE:
+ ret = fclose( file->handle.file ); /* passthrough */
+ break;
+
+ case CFTYPE_CURL:
+ /* make sure the easy handle is not in the multi handle anymore */
+ curl_multi_remove_handle( multi_handle, file->handle.curl );
+
+ /* cleanup */
+ curl_easy_cleanup( file->handle.curl );
+ break;
+
+ default: /* unknown or supported type - oh dear */
+ ret = EOF;
+ errno = EBADF;
+ break;
+ }
+
+ free( file->buffer ); /* free any allocated buffer space */
+ free( file );
+
+ return ret;
+}
+
+int url_feof( URL_FILE * file ) {
+ int ret = 0;
+
+ switch ( file->type ) {
+ case CFTYPE_FILE:
+ ret = feof( file->handle.file );
+ break;
+
+ case CFTYPE_CURL:
+ if ( ( file->buffer_pos == 0 ) && ( !file->still_running ) )
+ ret = 1;
+ break;
+
+ default: /* unknown or supported type - oh dear */
+ ret = -1;
+ errno = EBADF;
+ break;
+ }
+ return ret;
+}
+
+size_t url_fread( void *ptr, size_t size, size_t nmemb, URL_FILE * file ) {
+ size_t want;
+
+ switch ( file->type ) {
+ case CFTYPE_FILE:
+ want = fread( ptr, size, nmemb, file->handle.file );
+ break;
+
+ case CFTYPE_CURL:
+ want = nmemb * size;
+
+ fill_buffer( file, want );
+
+ /* check if there's data in the buffer - if not fill_buffer()
+ * either errored or EOF */
+ if ( !file->buffer_pos )
+ return 0;
+
+ /* ensure only available data is considered */
+ if ( file->buffer_pos < want )
+ want = file->buffer_pos;
+
+ /* xfer data to caller */
+ memcpy( ptr, file->buffer, want );
+
+ use_buffer( file, want );
+
+ want = want / size; /* number of items */
+ break;
+
+ default: /* unknown or supported type - oh dear */
+ want = 0;
+ errno = EBADF;
+ break;
+
+ }
+ return want;
+}
+
+char *url_fgets( char *ptr, size_t size, URL_FILE * file ) {
+ size_t want = size - 1; /* always need to leave room for zero termination */
+ size_t loop;
+
+ switch ( file->type ) {
+ case CFTYPE_FILE:
+ ptr = fgets( ptr, ( int ) size, file->handle.file );
+ break;
+
+ case CFTYPE_CURL:
+ fill_buffer( file, want );
+
+ /* check if there's data in the buffer - if not fill either errored or
+ * EOF */
+ if ( !file->buffer_pos )
+ return NULL;
+
+ /* ensure only available data is considered */
+ if ( file->buffer_pos < want )
+ want = file->buffer_pos;
+
+ /*buffer contains data */
+ /* look for newline or eof */
+ for ( loop = 0; loop < want; loop++ ) {
+ if ( file->buffer[loop] == '\n' ) {
+ want = loop + 1; /* include newline */
+ break;
+ }
+ }
+
+ /* xfer data to caller */
+ memcpy( ptr, file->buffer, want );
+ ptr[want] = 0; /* always null terminate */
+
+ use_buffer( file, want );
+
+ break;
+
+ default: /* unknown or supported type - oh dear */
+ ptr = NULL;
+ errno = EBADF;
+ break;
+ }
+
+ return ptr; /*success */
+}
+
+void url_rewind( URL_FILE * file ) {
+ switch ( file->type ) {
+ case CFTYPE_FILE:
+ rewind( file->handle.file ); /* passthrough */
+ break;
+
+ case CFTYPE_CURL:
+ /* halt transaction */
+ curl_multi_remove_handle( multi_handle, file->handle.curl );
+
+ /* restart */
+ curl_multi_add_handle( multi_handle, file->handle.curl );
+
+ /* ditch buffer - write will recreate - resets stream pos */
+ free( file->buffer );
+ file->buffer = NULL;
+ file->buffer_pos = 0;
+ file->buffer_len = 0;
+
+ break;
+
+ default: /* unknown or supported type - oh dear */
+ break;
+ }
+}
+
+#ifdef FOPEN_STANDALONE
+#define FGETSFILE "fgets.test"
+#define FREADFILE "fread.test"
+#define REWINDFILE "rewind.test"
+
+/* Small main program to retrieve from a url using fgets and fread saving the
+ * output to two test files (note the fgets method will corrupt binary files if
+ * they contain 0 chars */
+int main( int argc, char *argv[] ) {
+ URL_FILE *handle;
+ FILE *outf;
+
+ size_t nread;
+ char buffer[256];
+ const char *url;
+
+ CURL *curl;
+ CURLcode res;
+
+ curl_global_init( CURL_GLOBAL_DEFAULT );
+
+ curl = curl_easy_init( );
+
+
+ if ( argc < 2 )
+ url = "http://192.168.7.3/testfile"; /* default to testurl */
+ else
+ url = argv[1]; /* use passed url */
+
+ /* copy from url line by line with fgets */
+ outf = fopen( FGETSFILE, "wb+" );
+ if ( !outf ) {
+ perror( "couldn't open fgets output file\n" );
+ return 1;
+ }
+
+ handle = url_fopen( url, "r" );
+ if ( !handle ) {
+ printf( "couldn't url_fopen() %s\n", url );
+ fclose( outf );
+ return 2;
+ }
+
+ while ( !url_feof( handle ) ) {
+ url_fgets( buffer, sizeof( buffer ), handle );
+ fwrite( buffer, 1, strlen( buffer ), outf );
+ }
+
+ url_fclose( handle );
+
+ fclose( outf );
+
+
+ /* Copy from url with fread */
+ outf = fopen( FREADFILE, "wb+" );
+ if ( !outf ) {
+ perror( "couldn't open fread output file\n" );
+ return 1;
+ }
+
+ handle = url_fopen( "testfile", "r" );
+ if ( !handle ) {
+ printf( "couldn't url_fopen() testfile\n" );
+ fclose( outf );
+ return 2;
+ }
+
+ do {
+ nread = url_fread( buffer, 1, sizeof( buffer ), handle );
+ fwrite( buffer, 1, nread, outf );
+ } while ( nread );
+
+ url_fclose( handle );
+
+ fclose( outf );
+
+
+ /* Test rewind */
+ outf = fopen( REWINDFILE, "wb+" );
+ if ( !outf ) {
+ perror( "couldn't open fread output file\n" );
+ return 1;
+ }
+
+ handle = url_fopen( "testfile", "r" );
+ if ( !handle ) {
+ printf( "couldn't url_fopen() testfile\n" );
+ fclose( outf );
+ return 2;
+ }
+
+ nread = url_fread( buffer, 1, sizeof( buffer ), handle );
+ fwrite( buffer, 1, nread, outf );
+ url_rewind( handle );
+
+ buffer[0] = '\n';
+ fwrite( buffer, 1, 1, outf );
+
+ nread = url_fread( buffer, 1, sizeof( buffer ), handle );
+ fwrite( buffer, 1, nread, outf );
+
+ url_fclose( handle );
+
+ fclose( outf );
+
+ return 0; /* all done */
+}
+#endif
diff --git a/src/io/fopen.h b/src/io/fopen.h
new file mode 100644
index 0000000..5f87bd2
--- /dev/null
+++ b/src/io/fopen.h
@@ -0,0 +1,83 @@
+/*
+ * fopen.h
+ *
+ * adapted from https://curl.haxx.se/libcurl/c/fopen.html.
+ *
+ *
+ * Modifications to read/write wide character streams by
+ * Simon Brooke.
+ *
+ * NOTE THAT: for my purposes, I'm only interested in wide characters,
+ * and I always read them one character at a time.
+ *
+ * Copyright (c) 2003, 2017 Simtec Electronics
+ * Some portions (c) 2019 Simon Brooke
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This example requires libcurl 7.9.7 or later.
+ */
+
+#ifndef __fopen_h
+#define __fopen_h
+#include
+/*
+ * wide characters
+ */
+#include
+#include
+
+#define url_fwprintf(f, ...) ((f->type = CFTYPE_FILE) ? fwprintf( f->handle.file, __VA_ARGS__) : -1)
+#define url_fputws(ws, f) ((f->type = CFTYPE_FILE) ? fputws(ws, f->handle.file) : 0)
+#define url_fputwc(wc, f) ((f->type = CFTYPE_FILE) ? fputwc(wc, f->handle.file) : 0)
+
+enum fcurl_type_e {
+ CFTYPE_NONE = 0,
+ CFTYPE_FILE = 1,
+ CFTYPE_CURL = 2
+};
+
+struct fcurl_data {
+ enum fcurl_type_e type; /* type of handle */
+ union {
+ CURL *curl;
+ FILE *file;
+ } handle; /* handle */
+
+ char *buffer; /* buffer to store cached data */
+ size_t buffer_len; /* currently allocated buffer's length */
+ size_t buffer_pos; /* cursor into in buffer */
+ int still_running; /* Is background url fetch still in progress */
+};
+
+typedef struct fcurl_data URL_FILE;
+
+/* exported functions */
+URL_FILE *url_fopen( const char *url, const char *operation );
+int url_fclose( URL_FILE * file );
+int url_feof( URL_FILE * file );
+size_t url_fread( void *ptr, size_t size, size_t nmemb, URL_FILE * file );
+char *url_fgets( char *ptr, size_t size, URL_FILE * file );
+void url_rewind( URL_FILE * file );
+
+#endif
diff --git a/src/io/io.c b/src/io/io.c
new file mode 100644
index 0000000..d01f788
--- /dev/null
+++ b/src/io/io.c
@@ -0,0 +1,549 @@
+/*
+ * io.c
+ *
+ * Communication between PSSE and the outside world, via libcurl. NOTE
+ * that this file destructively changes metadata on URL connections,
+ * because the metadata is not available until the stream has been read
+ * from. It would be better to find a workaround!
+ *
+ * (c) 2019 Simon Brooke
+ * Licensed under GPL version 2.0, or, at your option, any later version.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+/*
+ * wide characters
+ */
+#include
+#include
+
+#include
+
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
+#include "debug.h"
+#include "io/fopen.h"
+#include "arith/integer.h"
+#include "ops/intern.h"
+#include "ops/lispops.h"
+#include "utils.h"
+
+/**
+ * The sharing hub for all connections. TODO: Ultimately this probably doesn't
+ * work for a multi-user environment and we will need one sharing hub for each
+ * user, or else we will need to not share at least cookies and ssl sessions.
+ */
+CURLSH *io_share;
+
+/**
+ * Allow a one-character unget facility. This may not be enough - we may need
+ * to allocate a buffer.
+ */
+wint_t ungotten = 0;
+
+/**
+ * Initialise the I/O subsystem.
+ *
+ * @return 0 on success; any other value means failure.
+ */
+int io_init( ) {
+ int result = curl_global_init( CURL_GLOBAL_SSL );
+
+ io_share = curl_share_init( );
+
+ if ( result == 0 ) {
+ curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT );
+ curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE );
+ curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS );
+ curl_share_setopt( io_share, CURLSHOPT_SHARE,
+ CURL_LOCK_DATA_SSL_SESSION );
+ curl_share_setopt( io_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL );
+ }
+
+ return result;
+}
+
+/**
+ * Convert this lisp string-like-thing (also works for symbols, and, later
+ * keywords) into a UTF-8 string. NOTE that the returned value has been
+ * malloced and must be freed. TODO: candidate to moving into a utilities
+ * file.
+ *
+ * @param s the lisp string or symbol;
+ * @return the c string.
+ */
+char *lisp_string_to_c_string( struct cons_pointer s ) {
+ char *result = NULL;
+
+ if ( stringp( s ) || symbolp( s ) ) {
+ int len = 0;
+
+ for ( struct cons_pointer c = s; !nilp( c );
+ c = pointer2cell( c ).payload.string.cdr ) {
+ len++;
+ }
+
+ wchar_t *buffer = calloc( len + 1, sizeof( wchar_t ) );
+ /* worst case, one wide char = four utf bytes */
+ result = calloc( ( len * 4 ) + 1, sizeof( char ) );
+
+ int i = 0;
+ for ( struct cons_pointer c = s; !nilp( c );
+ c = pointer2cell( c ).payload.string.cdr ) {
+ buffer[i++] = pointer2cell( c ).payload.string.character;
+ }
+
+ wcstombs( result, buffer, len );
+ free( buffer );
+ }
+
+ debug_print( L"lisp_string_to_c_string( ", DEBUG_IO );
+ debug_print_object( s, DEBUG_IO );
+ debug_printf( DEBUG_IO, L") => '%s'\n", result );
+
+ return result;
+}
+
+
+/**
+ * given this file handle f, return a new url_file handle wrapping it.
+ *
+ * @param f the file to be wrapped;
+ * @return the new handle, or null if no such handle could be allocated.
+ */
+URL_FILE *file_to_url_file( FILE * f ) {
+ URL_FILE *result = ( URL_FILE * ) malloc( sizeof( URL_FILE ) );
+
+ if ( result != NULL ) {
+ result->type = CFTYPE_FILE, result->handle.file = f;
+ }
+
+ return result;
+}
+
+
+/**
+ * get one wide character from the buffer.
+ *
+ * @param file the stream to read from;
+ * @return the next wide character on the stream, or zero if no more.
+ */
+wint_t url_fgetwc( URL_FILE * input ) {
+ wint_t result = -1;
+
+ if ( ungotten != 0 ) {
+ /* TODO: not thread safe */
+ result = ungotten;
+ ungotten = 0;
+ } else {
+ switch ( input->type ) {
+ case CFTYPE_FILE:
+ fwide( input->handle.file, 1 ); /* wide characters */
+ result = fgetwc( input->handle.file ); /* passthrough */
+ break;
+
+ case CFTYPE_CURL:{
+ char *cbuff =
+ calloc( sizeof( wchar_t ) + 2, sizeof( char ) );
+ wchar_t *wbuff = calloc( 2, sizeof( wchar_t ) );
+
+ size_t count = 0;
+
+ debug_print( L"url_fgetwc: about to call url_fgets\n",
+ DEBUG_IO );
+ url_fgets( cbuff, 2, input );
+ debug_print( L"url_fgetwc: back from url_fgets\n",
+ DEBUG_IO );
+ int c = ( int ) cbuff[0];
+ // TODO: risk of reading off cbuff?
+ debug_printf( DEBUG_IO,
+ L"url_fgetwc: cbuff is '%s'; (first) character = %d (%c)\n",
+ cbuff, c, c & 0xf7 );
+ /* The value of each individual byte indicates its UTF-8 function, as follows:
+ *
+ * 00 to 7F hex (0 to 127): first and only byte of a sequence.
+ * 80 to BF hex (128 to 191): continuing byte in a multi-byte sequence.
+ * C2 to DF hex (194 to 223): first byte of a two-byte sequence.
+ * E0 to EF hex (224 to 239): first byte of a three-byte sequence.
+ * F0 to FF hex (240 to 255): first byte of a four-byte sequence.
+ */
+ if ( c <= 0xf7 ) {
+ count = 1;
+ } else if ( c >= 0xc2 && c <= 0xdf ) {
+ count = 2;
+ } else if ( c >= 0xe0 && c <= 0xef ) {
+ count = 3;
+ } else if ( c >= 0xf0 && c <= 0xff ) {
+ count = 4;
+ }
+
+ if ( count > 1 ) {
+ url_fgets( ( char * ) &cbuff[1], count, input );
+ }
+ mbstowcs( wbuff, cbuff, 2 ); //(char *)(&input->buffer[input->buffer_pos]), 1 );
+ result = wbuff[0];
+
+ free( wbuff );
+ free( cbuff );
+ }
+ break;
+ case CFTYPE_NONE:
+ break;
+ }
+ }
+
+ debug_printf( DEBUG_IO, L"url_fgetwc returning %d (%C)\n", result,
+ result );
+ return result;
+}
+
+wint_t url_ungetwc( wint_t wc, URL_FILE * input ) {
+ wint_t result = -1;
+
+ switch ( input->type ) {
+ case CFTYPE_FILE:
+ fwide( input->handle.file, 1 ); /* wide characters */
+ result = ungetwc( wc, input->handle.file ); /* passthrough */
+ break;
+
+ case CFTYPE_CURL:{
+ ungotten = wc;
+ break;
+ case CFTYPE_NONE:
+ break;
+ }
+ }
+
+ return result;
+}
+
+
+/**
+ * Function, sort-of: close the file indicated by my first arg, and return
+ * nil. If the first arg is not a stream, does nothing. All other args are
+ * ignored.
+ *
+ * * (close stream)
+ *
+ * @param frame my stack_frame.
+ * @param frame_pointer a pointer to my stack_frame.
+ * @param env my environment.
+ * @return T if the stream was successfully closed, else NIL.
+ */
+struct cons_pointer
+lisp_close( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env ) {
+ struct cons_pointer result = NIL;
+
+ if ( readp( frame->arg[0] ) || writep( frame->arg[0] ) ) {
+ if ( url_fclose( pointer2cell( frame->arg[0] ).payload.stream.stream )
+ == 0 ) {
+ result = TRUE;
+ }
+ }
+
+ return result;
+}
+
+struct cons_pointer add_meta_integer( struct cons_pointer meta, wchar_t *key,
+ long int value ) {
+ return
+ make_cons( make_cons
+ ( c_string_to_lisp_keyword( key ),
+ make_integer( value, NIL ) ), meta );
+}
+
+struct cons_pointer add_meta_string( struct cons_pointer meta, wchar_t *key,
+ char *value ) {
+ value = trim( value );
+ wchar_t buffer[strlen( value ) + 1];
+ mbstowcs( buffer, value, strlen( value ) + 1 );
+
+ return make_cons( make_cons( c_string_to_lisp_keyword( key ),
+ c_string_to_lisp_string( buffer ) ), meta );
+}
+
+struct cons_pointer add_meta_time( struct cons_pointer meta, wchar_t *key,
+ time_t * value ) {
+ /* I don't yet have a concept of a date-time object, which is a
+ * bit of an oversight! */
+ char datestring[256];
+
+ strftime( datestring,
+ sizeof( datestring ),
+ nl_langinfo( D_T_FMT ), localtime( value ) );
+
+ return add_meta_string( meta, key, datestring );
+}
+
+/**
+ * Callback to assemble metadata for a URL stream. This is naughty because
+ * it modifies data, but it's really the only way to create metadata.
+ */
+static size_t write_meta_callback( char *string, size_t size, size_t nmemb,
+ struct cons_pointer stream ) {
+ struct cons_space_object *cell = &pointer2cell( stream );
+
+ /* make a copy of the string that we can destructively change */
+ char *s = calloc( strlen( string ), sizeof( char ) );
+
+ strcpy( s, string );
+
+ if ( strncmp( &cell->tag.bytes[0], READTAG, 4 ) ||
+ strncmp( &cell->tag.bytes[0], WRITETAG, 4 ) ) {
+ int offset = index_of( ':', s );
+
+ if ( offset != -1 ) {
+ s[offset] = ( char ) 0;
+ char *name = trim( s );
+ char *value = trim( &s[++offset] );
+ wchar_t wname[strlen( name )];
+
+ mbstowcs( wname, name, strlen( name ) + 1 );
+
+ cell->payload.stream.meta =
+ add_meta_string( cell->payload.stream.meta, wname, value );
+
+ debug_printf( DEBUG_IO,
+ L"write_meta_callback: added header '%s': value '%s'\n",
+ name, value );
+ } else if ( strncmp( "HTTP", s, 4 ) == 0 ) {
+ int offset = index_of( ' ', s );
+ char *value = trim( &s[offset] );
+
+ cell->payload.stream.meta =
+ add_meta_integer( add_meta_string
+ ( cell->payload.stream.meta, L"status",
+ value ), L"status-code", strtol( value,
+ NULL,
+ 10 ) );
+
+ debug_printf( DEBUG_IO,
+ L"write_meta_callback: added header 'status': value '%s'\n",
+ value );
+ } else {
+ debug_printf( DEBUG_IO,
+ L"write_meta_callback: header passed with no colon: '%s'\n",
+ s );
+ }
+ } else {
+ debug_print
+ ( L"Pointer passed to write_meta_callback did not point to a stream: ",
+ DEBUG_IO );
+ debug_dump_object( stream, DEBUG_IO );
+ }
+
+ free( s );
+ return strlen( string );
+}
+
+void collect_meta( struct cons_pointer stream, char *url ) {
+ struct cons_space_object *cell = &pointer2cell( stream );
+ URL_FILE *s = pointer2cell( stream ).payload.stream.stream;
+ struct cons_pointer meta =
+ add_meta_string( cell->payload.stream.meta, L"url", url );
+ struct stat statbuf;
+ int result = stat( url, &statbuf );
+ struct passwd *pwd;
+ struct group *grp;
+
+ switch ( s->type ) {
+ case CFTYPE_NONE:
+ break;
+ case CFTYPE_FILE:
+ if ( result == 0 ) {
+ if ( ( pwd = getpwuid( statbuf.st_uid ) ) != NULL ) {
+ meta = add_meta_string( meta, L"owner", pwd->pw_name );
+ } else {
+ meta = add_meta_integer( meta, L"owner", statbuf.st_uid );
+ }
+
+ if ( ( grp = getgrgid( statbuf.st_gid ) ) != NULL ) {
+ meta = add_meta_string( meta, L"group", grp->gr_name );
+ } else {
+ meta = add_meta_integer( meta, L"group", statbuf.st_gid );
+ }
+
+ meta =
+ add_meta_integer( meta, L"size",
+ ( intmax_t ) statbuf.st_size );
+
+ meta = add_meta_time( meta, L"modified", &statbuf.st_mtime );
+ }
+ break;
+ case CFTYPE_CURL:
+ curl_easy_setopt( s->handle.curl, CURLOPT_VERBOSE, 1L );
+ curl_easy_setopt( s->handle.curl, CURLOPT_HEADERFUNCTION,
+ write_meta_callback );
+ curl_easy_setopt( s->handle.curl, CURLOPT_HEADERDATA, stream );
+ break;
+ }
+
+ /* this is destructive change before the cell is released into the
+ * wild, and consequently permissible, just. */
+ cell->payload.stream.meta = meta;
+}
+
+/**
+ * Resutn the current default input, or of `inputp` is false, output stream from
+ * this `env`ironment.
+ */
+struct cons_pointer get_default_stream( bool inputp, struct cons_pointer env ) {
+ struct cons_pointer result = NIL;
+ struct cons_pointer stream_name =
+ c_string_to_lisp_symbol( inputp ? L"*in*" : L"*out*" );
+
+ inc_ref( stream_name );
+
+ result = c_assoc( stream_name, env );
+
+ dec_ref( stream_name );
+
+ return result;
+}
+
+
+/**
+ * Function: return a stream open on the URL indicated by the first argument;
+ * if a second argument is present and is non-nil, open it for reading. At
+ * present, further arguments are ignored and there is no mechanism to open
+ * to append, or error if the URL is faulty or indicates an unavailable
+ * resource.
+ *
+ * * (read-char stream)
+ *
+ * @param frame my stack_frame.
+ * @param frame_pointer a pointer to my stack_frame.
+ * @param env my environment.
+ * @return a string of one character, namely the next available character
+ * on my stream, if any, else NIL.
+ */
+struct cons_pointer
+lisp_open( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env ) {
+ struct cons_pointer result = NIL;
+
+ if ( stringp( frame->arg[0] ) ) {
+ char *url = lisp_string_to_c_string( frame->arg[0] );
+
+ if ( nilp( frame->arg[1] ) ) {
+ URL_FILE *stream = url_fopen( url, "r" );
+
+ debug_printf( DEBUG_IO,
+ L"lisp_open: stream @ %ld, stream type = %d, stream handle = %ld\n",
+ ( long int ) &stream, ( int ) stream->type,
+ ( long int ) stream->handle.file );
+
+ switch ( stream->type ) {
+ case CFTYPE_NONE:
+ return
+ make_exception( c_string_to_lisp_string
+ ( L"Could not open stream" ),
+ frame_pointer );
+ break;
+ case CFTYPE_FILE:
+ if ( stream->handle.file == NULL ) {
+ return
+ make_exception( c_string_to_lisp_string
+ ( L"Could not open file" ),
+ frame_pointer );
+ }
+ break;
+ case CFTYPE_CURL:
+ /* can't tell whether a URL is bad without reading it */
+ break;
+ }
+
+ result = make_read_stream( stream, NIL );
+ } else {
+ // TODO: anything more complex is a problem for another day.
+ URL_FILE *stream = url_fopen( url, "w" );
+ result = make_write_stream( stream, NIL );
+ }
+
+ if ( pointer2cell( result ).payload.stream.stream == NULL ) {
+ result = NIL;
+ } else {
+ collect_meta( result, url );
+ }
+
+ free( url );
+ }
+
+ return result;
+}
+
+/**
+ * Function: return the next character from the stream indicated by arg 0;
+ * further arguments are ignored.
+ *
+ * * (read-char stream)
+ *
+ * @param frame my stack_frame.
+ * @param frame_pointer a pointer to my stack_frame.
+ * @param env my environment.
+ * @return a string of one character, namely the next available character
+ * on my stream, if any, else NIL.
+ */
+struct cons_pointer
+lisp_read_char( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env ) {
+ struct cons_pointer result = NIL;
+
+ if ( readp( frame->arg[0] ) ) {
+ result =
+ make_string( url_fgetwc
+ ( pointer2cell( frame->arg[0] ).payload.stream.
+ stream ), NIL );
+ }
+
+ return result;
+}
+
+/**
+ * Function: return a string representing all characters from the stream
+ * indicated by arg 0; further arguments are ignored.
+ *
+ * * (slurp stream)
+ *
+ * @param frame my stack_frame.
+ * @param frame_pointer a pointer to my stack_frame.
+ * @param env my environment.
+ * @return a string of one character, namely the next available character
+ * on my stream, if any, else NIL.
+ */
+struct cons_pointer
+lisp_slurp( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env ) {
+ struct cons_pointer result = NIL;
+
+ if ( readp( frame->arg[0] ) ) {
+ URL_FILE *stream = pointer2cell( frame->arg[0] ).payload.stream.stream;
+ struct cons_pointer cursor = make_string( url_fgetwc( stream ), NIL );
+ result = cursor;
+
+ for ( wint_t c = url_fgetwc( stream ); !url_feof( stream ) && c != 0;
+ c = url_fgetwc( stream ) ) {
+ debug_print( L"slurp: cursor is: ", DEBUG_IO );
+ debug_dump_object( cursor, DEBUG_IO );
+ debug_print( L"; result is: ", DEBUG_IO );
+ debug_dump_object( result, DEBUG_IO );
+ debug_println( DEBUG_IO );
+
+ struct cons_space_object *cell = &pointer2cell( cursor );
+ cursor = make_string( ( wchar_t ) c, NIL );
+ cell->payload.string.cdr = cursor;
+ }
+ }
+
+ return result;
+}
diff --git a/src/io/io.h b/src/io/io.h
new file mode 100644
index 0000000..f350c13
--- /dev/null
+++ b/src/io/io.h
@@ -0,0 +1,40 @@
+
+/*
+ * io.h
+ *
+ * Communication between PSSE and the outside world, via libcurl.
+ *
+ * (c) 2019 Simon Brooke
+ * Licensed under GPL version 2.0, or, at your option, any later version.
+ */
+
+#ifndef __psse_io_h
+#define __psse_io_h
+#include
+#include "consspaceobject.h"
+
+extern CURLSH *io_share;
+
+int io_init( );
+
+URL_FILE *file_to_url_file( FILE * f );
+wint_t url_fgetwc( URL_FILE * input );
+wint_t url_ungetwc( wint_t wc, URL_FILE * input );
+
+struct cons_pointer get_default_stream( bool inputp, struct cons_pointer env );
+
+struct cons_pointer
+lisp_close( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+struct cons_pointer
+lisp_open( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+struct cons_pointer
+lisp_read_char( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+struct cons_pointer
+lisp_slurp( struct stack_frame *frame, struct cons_pointer frame_pointer,
+ struct cons_pointer env );
+
+
+#endif
diff --git a/src/io/print.c b/src/io/print.c
new file mode 100644
index 0000000..8f4b88e
--- /dev/null
+++ b/src/io/print.c
@@ -0,0 +1,281 @@
+/*
+ * print.c
+ *
+ * First pass at a printer, for bootstrapping.
+ *
+ * (c) 2017 Simon Brooke
+ * Licensed under GPL version 2.0, or, at your option, any later version.
+ */
+
+#include
+#include
+#include
+#include
+/*
+ * wide characters
+ */
+#include
+#include
+
+#include "memory/conspage.h"
+#include "memory/consspaceobject.h"
+#include "memory/hashmap.h"
+#include "arith/integer.h"
+#include "ops/intern.h"
+#include "memory/stack.h"
+#include "io/print.h"
+#include "time/psse_time.h"
+#include "memory/vectorspace.h"
+
+/**
+ * print all the characters in the symbol or string indicated by `pointer`
+ * onto this `output`; if `pointer` does not indicate a string or symbol,
+ * don't print anything but just return.
+ */
+void print_string_contents( URL_FILE * output, struct cons_pointer pointer ) {
+ while ( stringp( pointer ) || symbolp( pointer ) || keywordp( pointer ) ) {
+ struct cons_space_object *cell = &pointer2cell( pointer );
+ wchar_t c = cell->payload.string.character;
+
+ if ( c != '\0' ) {
+ url_fputwc( c, output );
+ }
+ pointer = cell->payload.string.cdr;
+ }
+}
+
+/**
+ * print all the characters in the string indicated by `pointer` onto
+ * the stream at this `output`, prepending and appending double quote
+ * characters.
+ */
+void print_string( URL_FILE * output, struct cons_pointer pointer ) {
+ url_fputwc( btowc( '"' ), output );
+ print_string_contents( output, pointer );
+ url_fputwc( btowc( '"' ), output );
+}
+
+/**
+ * Print a single list cell (cons cell) indicated by `pointer` to the
+ * stream indicated by `output`. if `initial_space` is `true`, prepend
+ * a space character.
+ */
+void
+print_list_contents( URL_FILE * output, struct cons_pointer pointer,
+ bool initial_space ) {
+ struct cons_space_object *cell = &pointer2cell( pointer );
+
+ switch ( cell->tag.value ) {
+ case CONSTV:
+ if ( initial_space ) {
+ url_fputwc( btowc( ' ' ), output );
+ }
+ print( output, cell->payload.cons.car );
+
+ print_list_contents( output, cell->payload.cons.cdr, true );
+ break;
+ case NILTV:
+ break;
+ default:
+ url_fwprintf( output, L" . " );
+ print( output, pointer );
+ }
+}
+
+void print_list( URL_FILE * output, struct cons_pointer pointer ) {
+ url_fputws( L"(", output );
+ print_list_contents( output, pointer, false );
+ url_fputws( L")", output );
+}
+
+void print_map( URL_FILE * output, struct cons_pointer map ) {
+ if ( hashmapp( map ) ) {
+ struct vector_space_object *vso = pointer_to_vso( map );
+
+ url_fputwc( btowc( '{' ), output );
+
+ for ( struct cons_pointer ks = hashmap_keys( map ); !nilp( ks );
+ ks = c_cdr( ks ) ) {
+ struct cons_pointer key = c_car( ks );
+ print( output, key );
+ url_fputwc( btowc( ' ' ), output );
+ print( output, hashmap_get( map, key ) );
+
+ if ( !nilp( c_cdr( ks ) ) ) {
+ url_fputws( L", ", output );
+ }
+ }
+
+ url_fputwc( btowc( '}' ), output );
+ }
+}
+
+void print_vso( URL_FILE * output, struct cons_pointer pointer ) {
+ struct vector_space_object *vso = pointer_to_vso( pointer );
+ switch ( vso->header.tag.value ) {
+ case HASHTV:
+ print_map( output, pointer );
+ break;
+ // \todo: others.
+ default:
+ fwprintf( stderr, L"Unrecognised vector-space type '%d'\n",
+ vso->header.tag.value );
+ }
+}
+
+/**
+ * stolen from https://stackoverflow.com/questions/11656241/how-to-print-uint128-t-number-using-gcc
+ */
+void print_128bit( URL_FILE * output, __int128_t n ) {
+ if ( n == 0 ) {
+ fwprintf( stderr, L"0" );
+ } else {
+ char str[40] = { 0 }; // log10(1 << 128) + '\0'
+ char *s = str + sizeof( str ) - 1; // start at the end
+ while ( n != 0 ) {
+ if ( s == str )
+ return; // never happens
+
+ *--s = "0123456789"[n % 10]; // save last digit
+ n /= 10; // drop it
+ }
+ url_fwprintf( output, L"%s", s );
+ }
+}
+
+
+/**
+ * Print the cons-space object indicated by `pointer` to the stream indicated
+ * by `output`.
+ */
+struct cons_pointer print( URL_FILE * output, struct cons_pointer pointer ) {
+ struct cons_space_object cell = pointer2cell( pointer );
+ char *buffer;
+
+ /*
+ * Because tags have values as well as bytes, this if ... else if
+ * statement can ultimately be replaced by a switch, which will be neater.
+ */
+ switch ( cell.tag.value ) {
+ case CONSTV:
+ print_list( output, pointer );
+ break;
+ case EXCEPTIONTV:
+ url_fputws( L"\nException: ", output );
+ dump_stack_trace( output, pointer );
+ break;
+ case FUNCTIONTV:
+ url_fputws( L"', output );
+ break;
+ case INTEGERTV:{
+ struct cons_pointer s = integer_to_string( pointer, 10 );
+ inc_ref( s );
+ print_string_contents( output, s );
+ dec_ref( s );
+ }
+ break;
+ case KEYTV:
+ url_fputws( L":", output );
+ print_string_contents( output, pointer );
+ break;
+ case LAMBDATV:{
+ url_fputws( L"', output );
+ }
+ break;
+ case NILTV:
+ url_fwprintf( output, L"nil" );
+ break;
+ case NLAMBDATV:{
+ url_fputws( L"', output );
+ }
+ break;
+ case RATIOTV:
+ print( output, cell.payload.ratio.dividend );
+ url_fputws( L"/", output );
+ print( output, cell.payload.ratio.divisor );
+ break;
+ case READTV:
+ url_fwprintf( output, L"', output );
+ break;
+ case REALTV:
+ /* \todo using the C heap is a bad plan because it will fragment.
+ * As soon as I have working vector space I'll use a special purpose
+ * vector space object */
+ buffer = ( char * ) malloc( 24 );
+ memset( buffer, 0, 24 );
+ /* format it really long, then clear the trailing zeros */
+ sprintf( buffer, "%-.23Lg", cell.payload.real.value );
+ if ( strchr( buffer, '.' ) != NULL ) {
+ for ( int i = strlen( buffer ) - 1; buffer[i] == '0'; i-- ) {
+ buffer[i] = '\0';
+ }
+ }
+ url_fwprintf( output, L"%s", buffer );
+ free( buffer );
+ break;
+ case STRINGTV:
+ print_string( output, pointer );
+ break;
+ case SYMBOLTV:
+ print_string_contents( output, pointer );
+ break;
+ case SPECIALTV:
+ url_fwprintf( output, L"', output );
+ break;
+ case TIMETV:
+ url_fwprintf( output, L"', output );
+ break;
+ case TRUETV:
+ url_fwprintf( output, L"t" );
+ break;
+ case VECTORPOINTTV:
+ print_vso( output, pointer );
+ break;
+ case WRITETV:
+ url_fwprintf( output, L"