aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format65
-rw-r--r--.gitattributes1
-rw-r--r--.github/workflows/docs.yaml32
-rw-r--r--.gitignore2
-rw-r--r--LICENSE674
-rw-r--r--README.rst112
-rw-r--r--VERSION1
-rwxr-xr-xbuild-avr8.sh18
-rwxr-xr-xbuild-docs.sh12
-rwxr-xr-xbuild-posix.sh16
-rw-r--r--docs/apply.rst63
-rw-r--r--docs/arch.rst155
-rw-r--r--docs/buff.rst29
-rw-r--r--docs/builtin.rst30
-rw-r--r--docs/cell.rst270
-rw-r--r--docs/conf.py23
-rw-r--r--docs/ctx.rst53
-rw-r--r--docs/env.rst34
-rw-r--r--docs/eval.rst48
-rw-r--r--docs/examples.rst20
-rw-r--r--docs/extensions.rst28
-rw-r--r--docs/function.rst83
-rw-r--r--docs/index.rst29
-rw-r--r--docs/introduction.rst69
-rw-r--r--docs/main.rst15
-rw-r--r--docs/mem.rst126
-rw-r--r--docs/read.rst160
-rw-r--r--docs/repl.rst40
-rw-r--r--docs/static/custom.css22
-rw-r--r--docs/status.rst76
-rw-r--r--docs/stream.rst54
-rw-r--r--docs/syntax.rst116
-rw-r--r--docs/write.rst27
-rw-r--r--examples/factorial.lsp6
-rwxr-xr-xformat.sh9
-rw-r--r--src_c/apply.c140
-rw-r--r--src_c/apply.h10
-rw-r--r--src_c/arch.h63
-rw-r--r--src_c/arch/avr8.c63
-rw-r--r--src_c/arch/avr8.h30
-rw-r--r--src_c/arch/posix.c54
-rw-r--r--src_c/arch/posix.h30
-rw-r--r--src_c/buff.c61
-rw-r--r--src_c/buff.h23
-rw-r--r--src_c/builtin.c65
-rw-r--r--src_c/builtin.h24
-rw-r--r--src_c/cell.c50
-rw-r--r--src_c/cell.h215
-rw-r--r--src_c/ctx.c231
-rw-r--r--src_c/ctx.h16
-rw-r--r--src_c/env.c86
-rw-r--r--src_c/env.h30
-rw-r--r--src_c/eval.c92
-rw-r--r--src_c/eval.h9
-rw-r--r--src_c/function.c582
-rw-r--r--src_c/function.h54
-rw-r--r--src_c/main.c55
-rw-r--r--src_c/mem.c478
-rw-r--r--src_c/mem.h189
-rw-r--r--src_c/read.c428
-rw-r--r--src_c/read.h10
-rw-r--r--src_c/repl.c104
-rw-r--r--src_c/repl.h9
-rw-r--r--src_c/status.h25
-rw-r--r--src_c/stream.c108
-rw-r--r--src_c/stream.h46
-rw-r--r--src_c/syntax.c192
-rw-r--r--src_c/syntax.h17
-rw-r--r--src_c/write.c174
-rw-r--r--src_c/write.h10
-rw-r--r--src_lsp/base-large.lsp117
-rw-r--r--src_lsp/base-small.lsp6
-rwxr-xr-xtest.sh19
-rw-r--r--test_c/acutest.h1839
-rw-r--r--test_c/cell.c239
-rw-r--r--test_c/mem.c38
76 files changed, 8549 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..8c67bcb
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,65 @@
+---
+Language: Cpp
+BasedOnStyle: LLVM
+AccessModifierOffset: -4
+AlignAfterOpenBracket: true
+AlignEscapedNewlinesLeft: false
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AlwaysBreakAfterDefinitionReturnType: false
+AlwaysBreakTemplateDeclarations: false
+AlwaysBreakBeforeMultilineStrings: false
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BinPackParameters: true
+BinPackArguments: true
+ColumnLimit: 80
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+DerivePointerAlignment: false
+ExperimentalAutoDetectBinPacking: false
+IndentCaseLabels: false
+IndentWrappedFunctionNames: false
+IndentFunctionDeclarationAfterType: false
+MaxEmptyLinesToKeep: 2
+KeepEmptyLinesAtTheStartOfBlocks: true
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakString: 1000
+PenaltyBreakFirstLessLess: 120
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+SpacesBeforeTrailingComments: 1
+Cpp11BracedListStyle: true
+Standard: Cpp11
+IndentWidth: 4
+TabWidth: 4
+UseTab: Never
+BreakBeforeBraces: Attach
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpacesInAngles: false
+SpaceInEmptyParentheses: false
+SpacesInCStyleCastParentheses: false
+SpaceAfterCStyleCast: false
+SpacesInContainerLiterals: true
+SpaceBeforeAssignmentOperators: true
+ContinuationIndentWidth: 4
+CommentPragmas: '^ IWYU pragma:'
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+SpaceBeforeParens: ControlStatements
+DisableFormat: false
+SortIncludes: false
+...
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5518465
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+test_c/acutest.h linguist-vendored
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 0000000..35aa304
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,32 @@
+name: docs
+on:
+ push:
+ branches:
+ - master
+jobs:
+ docs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout repository
+ uses: actions/checkout@v2
+ - name: install python
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.10"
+ - name: install requirements.pip.dev.txt
+ run: |
+ python -m pip install --upgrade pip
+ pip install sphinx furo
+ - name: build docs
+ run: ./build-docs.sh
+ - name: create CNAME
+ if: github.repository == 'bozokopic/lisp16'
+ run: |
+ echo "lisp16.kopic.xyz" > build/docs/CNAME
+ - name: deploy
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./build/docs
+ publish_branch: gh-pages
+ force_orphan: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea36607
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/build
+/compile_flags.txt
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9cecc1d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ {one line to give the program's name and a brief idea of what it does.}
+ Copyright (C) {year} {name of author}
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ {project} Copyright (C) {year} {fullname}
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..531c9fa
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,112 @@
+lisp16
+======
+
+Simple cross platform 16bit Lisp interpreter.
+
+* documentation - `<https://lisp16.kopic.xyz>`_
+* git repository - `<https://github.com/bozokopic/lisp16.git>`_
+
+
+About
+-----
+
+This project is intended as case study of Lisp implementation with following
+features:
+
+ * cross platform compatibility
+ * support for 8bit/16bit architectures (microcontrollers)
+ * possibility of running on POSIX systems
+ * small platform compatibility layer
+ * reduced Lisp dialect mostly based on Scheme
+ * LISP-1 namespace
+ * tail call optimization
+ * macros
+ * tree walking interpreter
+ * possibility of multiple independent interpreter instances as part of
+ single process
+ * simple mark and sweep garbage collection
+ * API design taking into account possibility of usage with foreign
+ function interface
+ * intended for educational/testing purposes
+ * emphasis on source code functionality organization over binary size
+ or lines of code (or other kinds of optimization)
+ * accompanied documentation with implementation explanation
+
+Following features are considered out of scope for this project:
+
+ * real-life production usage
+ * execution speed optimization
+ * virtual machine or machine code compilation
+ * rich standard library
+
+
+Building and usage
+------------------
+
+8bit AVR microcontrollers
+'''''''''''''''''''''''''
+
+Build targeting 8bit AVR microcontrollers (tested with ATmega328P) depends
+on `avr-gcc` and associated `binutils`.
+
+Shell script::
+
+ $ ./build-avr8.sh
+
+produces ``build/avr8/lisp16.bin`` binary that can be written to
+microcontroller's flash.
+
+After writing binary to flash, microcontroller will execute `lisp16` repl with
+UART used as input/output port.
+
+
+POSIX system
+''''''''''''
+
+Build targeting POSIX systems depends on C compiler (gcc with musl C library is
+used by default) and implementation of `getchar`/`putchar` and `malloc`/`free`
+functions (`malloc` can be replaced with static allocation in case multiple
+interpreter instances are not needed).
+
+Shell script::
+
+ $ ./build-posix.sh
+
+produces ``build/posix/lisp16`` static binary.
+
+Execution of ``build/posix/lisp16`` starts `lisp16` repl `stdin`/`stdout` used
+as input/output port.
+
+
+Documentation
+'''''''''''''
+
+Building of documentation depends on python with sphinx and furo modules
+available.
+
+Shell script::
+
+ $ ./build-docs.sh
+
+produces ``build/docs`` folder containing documentation.
+
+
+License
+-------
+
+lisp16 - simple cross platform 16bit Lisp interpreter
+
+Copyright (C) 2022 Bozo Kopic
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..6e8bf73
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.1.0
diff --git a/build-avr8.sh b/build-avr8.sh
new file mode 100755
index 0000000..fb590b1
--- /dev/null
+++ b/build-avr8.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname -- "$0")
+
+MCU=atmega328p
+FREQ=16000000UL
+ARCH=LSP_ARCH_AVR8
+CFLAGS="-Os"
+SRC="src_c/arch/avr8.c src_c/*.c"
+OUT_ELF=build/avr8/lisp16.elf
+OUT_BIN=build/avr8/lisp16.bin
+
+mkdir -p build/avr8
+
+avr-gcc -mmcu=$MCU -DF_CPU=$FREQ -DLSP_ARCH=$ARCH $CFLAGS -o $OUT_ELF $SRC
+avr-objcopy -O binary -j .text -j .data $OUT_ELF $OUT_BIN
diff --git a/build-docs.sh b/build-docs.sh
new file mode 100755
index 0000000..5efccc5
--- /dev/null
+++ b/build-docs.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname -- "$0")
+
+PYTHON=${PYTHON:-python3}
+SRC=docs
+DST=build/docs
+
+mkdir -p $DST
+$PYTHON -m sphinx -q -b html $SRC $DST
diff --git a/build-posix.sh b/build-posix.sh
new file mode 100755
index 0000000..6472b80
--- /dev/null
+++ b/build-posix.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname -- "$0")
+
+CC=${CC:-musl-gcc}
+SRC="src_c/arch/posix.c src_c/*.c"
+OUT=build/posix/lisp16
+ARCH=LSP_ARCH_POSIX
+CFLAGS="-O2"
+# CFLAGS="-O0 -ggdb"
+
+mkdir -p build/posix
+
+$CC -DLSP_ARCH=$ARCH $CFLAGS -o $OUT -static $SRC
diff --git a/docs/apply.rst b/docs/apply.rst
new file mode 100644
index 0000000..e45c31b
--- /dev/null
+++ b/docs/apply.rst
@@ -0,0 +1,63 @@
+Function/syntax application
+===========================
+
+Builtin function/syntax application
+-----------------------------------
+
+Data representing builtin function/syntax, together with type identification,
+contains number representing index of builtin entry. As described in previous
+chapters, each builtin entry contains function pointer:
+
+.. code-block:: c
+
+ typedef lsp_status_t (*lsp_builtin_cb_t)(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+
+Application of builtin function/syntax calls provided native function
+with arguments provided to function/syntax application.
+
+Only difference between builtin function and syntax application is in arguments
+provided to application itself. In case of function application, these
+arguments are previously evaluated and in case of syntax, they are provided
+without previous evaluation.
+
+
+User defined function/syntax application
+----------------------------------------
+
+Data representation of function/syntax references:
+
+ * parent context
+ * argument name list
+ * function/syntax body
+
+During function/syntax application, new context is created as copy of
+associated parent context. That context is used for evaluation of
+function/syntax content. Prior to content evaluation, provided arguments
+are added as new entries in context and are associated with symbols
+from argument name list based on their position in list. Once context
+is created and argument values are associated, function/syntax body is
+evaluated. Evaluation of function/syntax body is sequential evaluation
+of all available expression (from first to last) where value of last expression
+is used as resulting value.
+
+In case of function application, result of last expression evaluation is
+also value of function application itself. In case of syntax evaluation,
+result of last expression is data that is additionally evaluated in context
+from which syntax application was called.
+
+
+Source code
+-----------
+
+apply.h
+'''''''
+
+.. literalinclude:: ../src_c/apply.h
+ :language: c
+
+
+apply.c
+'''''''
+
+.. literalinclude:: ../src_c/apply.c
+ :language: c
diff --git a/docs/arch.rst b/docs/arch.rst
new file mode 100644
index 0000000..9eededf
--- /dev/null
+++ b/docs/arch.rst
@@ -0,0 +1,155 @@
+Architecture abstraction layer
+==============================
+
+To enable easier targeting of different execution platforms and provide
+decoupling from standard C library (or other dependencies), thin abstraction
+layer is required.
+
+
+Supported target platforms
+--------------------------
+
+Constant definition ``LSP_ARCH`` is used as identifier of desired target
+platform. This definition should be set during C source preprocessing to
+one of supported values:
+
+ * ``LSP_ARCH_POSIX`` (POSIX target)
+ * ``LSP_ARCH_AVR8`` (8bit AVR target)
+
+Depending on value of this constant, different platform specific
+implementations will be included.
+
+
+C data types
+------------
+
+`arch.h` defines fixed length integers (``lsp_int8_t``, ``lsp_int16_t``,
+``lsp_int32_t``, ``lsp_uint8_t``, ``lsp_uint16_t``, ``lsp_uint32_t``)
+independent of target platform. If target has available standard C library,
+this types are aliases to types defined by `stdint.h`. In case target doesn't
+have standard C library available, these types should be defined by
+appropriate C integer types (``char``, ``short``, ``int``, ``long``, ...).
+
+Additionally, ``lsp_bool_t`` is defined as alias to ``_Bool``. If ``_Bool``
+type is not available, ``lsp_uint8_t`` can be used. Care is taken not to depend
+on specifics of integer to ``_Bool`` conversions so that other integer types
+could be used as ``lsp_bool_t``.
+
+
+Platform specific functions
+---------------------------
+
+Each platform abstraction layer implementation (`arch/*.c`) is responsible
+for functions aliased with following names:
+
+ * ``LSP_ARCH_INIT``
+
+ Function, called before any other function, responsible for
+ initialization of platform specific state.
+
+ * ``LSP_ARCH_CREATE_MEM``
+
+ Function responsible for allocating memory that will represent
+ ``lsp_mem_t`` structure (described in following chapters) and it's
+ initialization.
+
+ * ``LSP_ARCH_FREE_MEM``
+
+ Function responsible for freeing previously allocated memory.
+
+ * ``LSP_ARCH_CREATE_IN_STREAM``
+
+ Function responsible for allocating memory that will represent
+ ``lsp_in_stream_t`` structure (described in following chapters) and
+ it's initialization.
+
+ * ``LSP_ARCH_FREE_IN_STREAM``
+
+ Function responsible for freeing previously allocated memory.
+
+ * ``LSP_ARCH_CREATE_OUT_STREAM``
+
+ Function responsible for allocating memory that will represent
+ ``lsp_out_stream_t`` structure (described in following chapters) and
+ it's initialization.
+
+ * ``LSP_ARCH_FREE_OUT_STREAM``
+
+ Function responsible for freeing previously allocated memory.
+
+In case of memory constrained targets (e.g. ``LSP_ARCH_AVR8``), functions
+responsible for allocating memory can return pointers to statically
+preallocated memory instead of dynamically allocated. For this target
+platforms, associated freeing functions don't implement any functionality.
+
+
+Input stream
+------------
+
+During initialization of input stream, platform specific implementation
+is responsible for providing function pointer with signature:
+
+.. code-block:: c
+
+ typedef lsp_int16_t (*lsp_stream_getchar_t)(lsp_in_stream_t *s);
+
+Provided function is responsible for reading single character from input
+stream (used by REPL). In case of POSIX, it's behavior corresponds to
+``getchar`` provided by standard C library.
+
+Details of input stream implementation are available in following chapters.
+
+
+Output stream
+-------------
+
+During initialization of output stream, platform specific implementation
+is responsible for providing function pointer with signature:
+
+.. code-block:: c
+
+ typedef lsp_int16_t (*lsp_stream_putchar_t)(lsp_out_stream_t *s, lsp_int16_t v);
+
+Provided function is responsible for writing single character to output
+stream (used by REPL). In case of POSIX, it's behavior corresponds to
+``putchar`` provided by standard C library.
+
+Details of output stream implementation are available in following chapters.
+
+
+Source code
+-----------
+
+arch.h
+''''''
+
+.. literalinclude:: ../src_c/arch.h
+ :language: c
+
+
+arch/avr8.h
+'''''''''''
+
+.. literalinclude:: ../src_c/arch/avr8.h
+ :language: c
+
+
+arch/avr8.c
+'''''''''''
+
+.. literalinclude:: ../src_c/arch/avr8.c
+ :language: c
+
+
+arch/posix.h
+''''''''''''
+
+.. literalinclude:: ../src_c/arch/posix.h
+ :language: c
+
+
+arch/posix.c
+''''''''''''
+
+.. literalinclude:: ../src_c/arch/posix.c
+ :language: c
diff --git a/docs/buff.rst b/docs/buff.rst
new file mode 100644
index 0000000..b052483
--- /dev/null
+++ b/docs/buff.rst
@@ -0,0 +1,29 @@
+String buffer
+=============
+
+While reading character data from input stream, buffer used for storing
+string of arbitrary length is required. `lsp_buff_t` provides preallocated
+limited temporary storage which is used as intermediary buffer in construction
+of `string` data with arbitrary size.
+
+After initialization with `lsp_buff_init`, characters are added to buffer
+with `lsp_buff_push` function. Once all characters are appended, single
+`string` data containing all previously appended characters, can be obtained
+with `lsp_buff_pop` function.
+
+
+Source code
+-----------
+
+buff.h
+''''''
+
+.. literalinclude:: ../src_c/buff.h
+ :language: c
+
+
+buff.c
+''''''
+
+.. literalinclude:: ../src_c/buff.c
+ :language: c
diff --git a/docs/builtin.rst b/docs/builtin.rst
new file mode 100644
index 0000000..bb87f8c
--- /dev/null
+++ b/docs/builtin.rst
@@ -0,0 +1,30 @@
+Builtin
+=======
+
+Builtin functions and builtin syntaxes are represented with
+`lsp_builtin_entry_t` structures. Each entry is defined with unique
+name and function pointer:
+
+.. code-block:: c
+
+ typedef lsp_status_t (*lsp_builtin_cb_t)(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+
+Associated function pointer is called during function/syntax application
+(described in following chapters).
+
+
+Source code
+-----------
+
+builtin.h
+'''''''''
+
+.. literalinclude:: ../src_c/builtin.h
+ :language: c
+
+
+builtin.c
+'''''''''
+
+.. literalinclude:: ../src_c/builtin.c
+ :language: c
diff --git a/docs/cell.rst b/docs/cell.rst
new file mode 100644
index 0000000..7e66897
--- /dev/null
+++ b/docs/cell.rst
@@ -0,0 +1,270 @@
+Data types
+==========
+
+All data types are encoded as one or more consecutive 16bit words (cells).
+Most significant bit of each 16bit word is reserved for memory manager usage
+and remaining 15bits are used for identifying data types and encoding
+data values. Most significant data bits of first word identifies
+data type. Most significant bit is referenced as bit 15 and least significant
+bit as bit 0.
+
+Implementation mostly uses static inline functions defined in header file,
+instead of preprocessor definitions, to provide API more suitable for
+foreign function interface.
+
+
+Number
+------
+
+Data type representing signed integer values of arbitrary length. Although
+encoding itself doesn't limit value size, to provide easier interface for
+data manipulation, values are limited to signed integers represented with
+32bit dual complement encoding. Single number is encoded with one or more
+words:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 0 1 s v v v v v v v v v v v v |
+ +---------+---------------------------------+
+ | n + 1 | m 1 v v v v v v v v v v v v v v |
+ +---------+---------------------------------+
+ | ... | ... |
+ +---------+---------------------------------+
+ | n + i | m 1 v v v v v v v v v v v v v v |
+ +---------+---------------------------------+
+ | ... | ... |
+ +---------+---------------------------------+
+ | n + m | m 0 v v v v v v v v v v v v v v |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bit 14 of first word (``0``) identifies number type.
+
+ * Bit 13 of first word and bit 14 of other words represents "more follows".
+ Only in last word (`n + m`) is this bit set to ``0``.
+
+ * Bit 12 of first word (`s`) identifies sign.
+
+ * Rest of bits are used as dual complement encoded integer value where
+ word `n` contains most significant bits and word `n + m` contains
+ least significant bits.
+
+
+Pair
+----
+
+Data type representing two addresses referencing word locations (usually
+known as cons cell). Address values are limited to 14bit unsigned integers
+which enables encoding of this type with two words:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 0 a a a a a a a a a a a a a |
+ +---------+---------------------------------+
+ | n + 1 | m a b b b b b b b b b b b b b b |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bits 14 and 13 of first word (``10``) identify pair type.
+
+ * 14 `a` bits encode first address value.
+
+ * 14 `b` bits encode second address value.
+
+
+String
+------
+
+Data type representing zero of more 8bit values. Single string is represented
+with one or more words:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 0 0 s s s s s s s s s s s |
+ +---------+---------------------------------+
+ | n + 1 | m a a a a a a a a b b b b b b b |
+ +---------+---------------------------------+
+ | n + 2 | m b c c c c c c c c d d d d d d |
+ +---------+---------------------------------+
+ | ... | ... |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12 and 11 of first word (``1100``) identify string type.
+
+ * 11 `s` bits represent string length (maximum string length is 2047).
+
+ * Bits `a`, `b`, `c`, ... represent 8bit values.
+
+This encoding schema tries to optimize memory usage but at the same time
+introduces significant overhead in manipulating string data.
+
+
+Symbol
+------
+
+Symbols are used as human readable labels associated with data values. They
+are encoded as 8bit characters similarly as string data:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 0 1 s s s s s s s s s s s |
+ +---------+---------------------------------+
+ | n + 1 | m a a a a a a a a b b b b b b b |
+ +---------+---------------------------------+
+ | n + 2 | m b c c c c c c c c d d d d d d |
+ +---------+---------------------------------+
+ | ... | ... |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12 and 11 of first word (``1101``) identify symbol type.
+
+ * 11 `s` bits represent symbol length (maximum symbol length is 2047).
+
+ * Bits `a`, `b`, `c`, ... represent 8bit character values.
+
+
+Builtin function
+----------------
+
+Builtin functions are referenced by function's index and encoded with
+single word:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 1 0 0 i i i i i i i i i i |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12, 11 and 10 (``11100``) identify builtin function type.
+
+ * 10 `i` bits represent builtin function index.
+
+
+Builtin syntax
+--------------
+
+Builtin syntaxes are referenced by syntax's index and encoded with
+single word:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 1 0 1 i i i i i i i i i i |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12, 11 and 10 (``11101``) identify builtin syntax type.
+
+ * 10 `i` bits represent builtin syntax index.
+
+
+Function
+--------
+
+Functions are defined by parent context, list of argument names and function
+body. Type identifier together with 14bit addressees of associated values are
+encoded within 4 words:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 1 1 0 x x x x x x x x x x |
+ +---------+---------------------------------+
+ | n + 1 | m x c c c c c c c c c c c c c c |
+ +---------+---------------------------------+
+ | n + 2 | m x a a a a a a a a a a a a a a |
+ +---------+---------------------------------+
+ | n + 3 | m x b b b b b b b b b b b b b b |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12, 11 and 10 of first word (``11110``) identify function
+ type.
+
+ * 14 `c` bits represent parent context address.
+
+ * 14 `a` bits represent argument name list address.
+
+ * 14 `b` bits represent body definition address.
+
+ * `x` bits are not used.
+
+
+Syntax
+------
+
+Syntaxes are defined by parent context, list of argument names and syntax
+body. Type identifier together with 14bit addressees of associated values are
+encoded within 4 words:
+
+ +---------+---------------------------------+
+ | address | data |
+ +=========+=================================+
+ | n | m 1 1 1 1 0 x x x x x x x x x x |
+ +---------+---------------------------------+
+ | n + 1 | m x c c c c c c c c c c c c c c |
+ +---------+---------------------------------+
+ | n + 2 | m x a a a a a a a a a a a a a a |
+ +---------+---------------------------------+
+ | n + 3 | m x b b b b b b b b b b b b b b |
+ +---------+---------------------------------+
+
+where:
+
+ * Bit 15 of each word (`m`) is reserved for memory management.
+
+ * Bits 14, 13, 12, 11 and 10 of first word (``11110``) identify syntax
+ type.
+
+ * 14 `c` bits represent parent context address.
+
+ * 14 `a` bits represent argument name list address.
+
+ * 14 `b` bits represent body definition address.
+
+ * `x` bits are not used.
+
+
+Source code
+-----------
+
+cell.h
+''''''
+
+.. literalinclude:: ../src_c/cell.h
+ :language: c
+
+
+cell.c
+''''''
+
+.. literalinclude:: ../src_c/cell.c
+ :language: c
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..d9d0dc9
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,23 @@
+from pathlib import Path
+
+root_path = Path(__file__).parent.parent.resolve()
+
+extensions = ['sphinx.ext.todo']
+
+version = (root_path / 'VERSION').read_text(encoding='utf-8').strip()
+project = 'lisp16'
+copyright = '2022, Bozo Kopic'
+master_doc = 'index'
+
+html_theme = 'furo'
+html_static_path = ['static']
+html_css_files = ['custom.css']
+html_use_index = False
+html_show_sourcelink = False
+html_show_sphinx = False
+html_sidebars = {'**': ["sidebar/brand.html",
+ "sidebar/scroll-start.html",
+ "sidebar/navigation.html",
+ "sidebar/scroll-end.html"]}
+
+todo_include_todos = True
diff --git a/docs/ctx.rst b/docs/ctx.rst
new file mode 100644
index 0000000..7eaa6d2
--- /dev/null
+++ b/docs/ctx.rst
@@ -0,0 +1,53 @@
+Context
+=======
+
+Context (also known as environment in some of Lisp implementations)
+is association between symbols and their data values that apply to
+specific evaluation scope. This implementation provides single
+lexical context for evaluation of all names regarding of associated data
+type (also known as Lisp-1 namespaces).
+
+Basic method of introducing new scope is function definition and application.
+During function definition, new function is associated with scope in which
+function itself is defined. Exact copy of current context as is in moment
+of definition is used as functions parent scope. During function application
+new context is created which inherit all associations that were available
+in parent scope.
+
+Initial context, which is used as starting context, is initialized with
+associations to builtin functions and syntaxes.
+
+Each context contains arbitrary number of mutable entries. Single entry
+defines association where any kind of data is referenced by symbol.
+Together with functions that add new entries (`lsp_ctx_add`) or obtain
+data associated with provided symbol (`lsp_ctx_get`), context enables
+modification of data referenced by provided symbol (`lsp_ctx_set`).
+During entry modification, previous data instance itself is not changed, only
+entry reference is modified to point to newly provided data instance.
+If child context modifies existing entry in parent context, this modifications
+will also be visible in parent context.
+
+Because of support for
+`tail call optimization <https://en.wikipedia.org/wiki/Tail_call>`_,
+implementation of context hierarchy relies on `lsp_ctx_copy` operation instead
+of referencing parent context from child context. This method induces
+additional overhead in context operation. Never the less, additional memory
+allocation overhead is mostly neutralized by usage of immutable linked list
+as basis for entry storage.
+
+
+Source code
+-----------
+
+ctx.h
+'''''
+
+.. literalinclude:: ../src_c/ctx.h
+ :language: c
+
+
+ctx.c
+'''''
+
+.. literalinclude:: ../src_c/ctx.c
+ :language: c
diff --git a/docs/env.rst b/docs/env.rst
new file mode 100644
index 0000000..5a4a243
--- /dev/null
+++ b/docs/env.rst
@@ -0,0 +1,34 @@
+Environment
+===========
+
+Environment (not to be mixed with context) represent current state of
+interpreter instance. It contains reference to memory, input stream and
+output stream. To enable
+`tail call optimization <https://en.wikipedia.org/wiki/Tail_call>`_,
+environment is also used as storage for next expression evaluation.
+
+Main method responsible for environment evaluation is `lsp_env_resolve`.
+This function implements evaluation loop (also known as trampoline), which
+iteratively evaluates sequence of expressions. Evaluation of single expression
+can result in direct data value (which is registered with
+`lsp_env_set_result_value` function) or can be delegated to execution
+of another expression (which is registered with `lsp_env_set_result_eval`
+function). Evaluation loop (trampoline) repeats expression evaluation
+until resulting data value is fully resolved.
+
+
+Source code
+-----------
+
+env.h
+'''''
+
+.. literalinclude:: ../src_c/env.h
+ :language: c
+
+
+env.c
+'''''
+
+.. literalinclude:: ../src_c/env.c
+ :language: c
diff --git a/docs/eval.rst b/docs/eval.rst
new file mode 100644
index 0000000..1e66698
--- /dev/null
+++ b/docs/eval.rst
@@ -0,0 +1,48 @@
+Evaluation
+==========
+
+Because Lisp code is represented with data structures, each data structure
+can be used as interpreter instruction. Evaluation of data, when used as
+interpreter expression, is defined according to data type:
+
+ * number, string, builtin function, builtin syntax, function, syntax
+
+ Data of this type evaluate to itself (expression consisting of
+ single instance of data evaluates to provided data instance).
+
+ * symbol
+
+ Symbols evaluate to data associated to provided symbol in
+ current evaluation context. If context entry associated with the symbol
+ is not available, evaluation error is signaled.
+
+ * pair/list
+
+ Lists evaluate to function/syntax application. As first step,
+ first element of list is evaluated. If first element evaluates
+ to function or builtin function, all remaining elements are also
+ evaluated and used as provided arguments. If first element evaluates
+ to syntax or builtin syntax, remaining list elements are not evaluated
+ and are used as provided arguments. After evaluation of first element
+ and possible argument evaluation (in case of functions), evaluation
+ is delegated to function/syntax application (described in following
+ chapters). If first list element doesn't evaluate to function/syntax,
+ evaluation error is signaled. Exception to this rule is empty
+ list which evaluates to itself.
+
+
+Source code
+-----------
+
+eval.h
+''''''
+
+.. literalinclude:: ../src_c/eval.h
+ :language: c
+
+
+eval.c
+''''''
+
+.. literalinclude:: ../src_c/eval.c
+ :language: c
diff --git a/docs/examples.rst b/docs/examples.rst
new file mode 100644
index 0000000..7905ef4
--- /dev/null
+++ b/docs/examples.rst
@@ -0,0 +1,20 @@
+Examples
+========
+
+Examples are run using interpreter build for POSIX platform. Before
+executing examples, interpreter is bootstrapped with `base-large.lsp`
+extensions.
+
+
+Factorial
+---------
+
+Simple implementation of factorials calculation.
+
+.. literalinclude:: ../examples/factorial.lsp
+ :language: scheme
+
+Example execution::
+
+ $ cat src_lsp/base-large.lsp examples/factorial.lsp | build/posix/lisp16
+ 3628800
diff --git a/docs/extensions.rst b/docs/extensions.rst
new file mode 100644
index 0000000..97eab1e
--- /dev/null
+++ b/docs/extensions.rst
@@ -0,0 +1,28 @@
+Extensions
+==========
+
+Base Lisp language, provided by interpreter with builtin functions/syntaxes,
+is deliberately designed with limited set of functionality.
+
+To provide more usable development environment, additional extensions
+written in Lisp are available. These extensions are implemented as set of
+instructions that should be evaluated by interpreter immediately after
+startup. After this bootstrapping evaluation finished, additional
+functionalities are available as part of active context.
+
+
+Source code
+-----------
+
+base-small.lsp
+''''''''''''''
+
+.. literalinclude:: ../src_lsp/base-small.lsp
+ :language: scheme
+
+
+base-large.lsp
+''''''''''''''
+
+.. literalinclude:: ../src_lsp/base-large.lsp
+ :language: scheme
diff --git a/docs/function.rst b/docs/function.rst
new file mode 100644
index 0000000..4f895a5
--- /dev/null
+++ b/docs/function.rst
@@ -0,0 +1,83 @@
+Builtin functions
+=================
+
+In following examples, lines starting with ``>`` represent characters
+provided to input stream. Lines without starting ``>`` character represent
+evaluation results written to output stream.
+
+Available builtin functions:
+
+ * `eval`
+
+ * `apply`
+
+ * `error`
+
+ * `cons`
+
+ * `set-car!`
+
+ * `set-cdr!`
+
+ * `number?`
+
+ * `pair?`
+
+ * `string?`
+
+ * `symbol?`
+
+ * `function?`
+
+ * `syntax?`
+
+ * `eq?`
+
+ * `equal?`
+
+ * `>`
+
+ * `<`
+
+ * `+`
+
+ * `-`
+
+ * `*`
+
+ * `/`
+
+ * `read`
+
+ * `read-u8`
+
+ * `peek-u8`
+
+ * `write`
+
+ * `write-u8`
+
+ * `make-string`
+
+ * `string-length`
+
+ * `string-ref`
+
+ * `string-set!`
+
+
+Source code
+-----------
+
+function.h
+''''''''''
+
+.. literalinclude:: ../src_c/function.h
+ :language: c
+
+
+function.c
+''''''''''
+
+.. literalinclude:: ../src_c/function.c
+ :language: c
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..11ca931
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,29 @@
+.. include:: ../README.rst
+
+
+Content
+-------
+
+.. toctree::
+ :maxdepth: 1
+
+ introduction
+ arch
+ status
+ cell
+ mem
+ stream
+ buff
+ read
+ write
+ builtin
+ ctx
+ env
+ eval
+ apply
+ syntax
+ function
+ repl
+ main
+ extensions
+ examples
diff --git a/docs/introduction.rst b/docs/introduction.rst
new file mode 100644
index 0000000..e4d57be
--- /dev/null
+++ b/docs/introduction.rst
@@ -0,0 +1,69 @@
+Introduction
+============
+
+Modern `microcontrollers <https://en.wikipedia.org/wiki/Microcontroller>`_
+provide powerful computational platforms in form of
+affordable and widely usable integrated circuit packages. Although, execution
+performance is often more than enough for execution of complex algorithms,
+some of constraints can represent challenge in implementing certain kind of
+applications. In case of interpreters for high level languages, amount of
+available RAM is significant factor which should be taken into account during
+design of interpreter and interpreted applications. This project explores
+these and similar impacts by implementing Lisp interpreter capable of
+interactive execution on 8bit/32bit microcontrollers.
+
+`Lisp <https://en.wikipedia.org/wiki/Lisp_(programming_language)>`_ is family
+of high level programming languages/dialects characterized with
+concise `homoiconic <https://en.wikipedia.org/wiki/Homoiconicity>`_ syntax.
+`Scheme <https://en.wikipedia.org/wiki/Scheme_(programming_language)>`_, as one
+of Lisp dialects, provides few powerful core functionalities which can be used
+to extend language itself with wide variety of derived constructs applicable to
+different programming paradigms and domains. Therefor, reduced core of Scheme,
+with only few data types and builtin functions/syntaxes, can provide good base
+system for execution of high level applications on embedded/constrain
+platforms.
+
+One of characteristics of most Lisp implementations is support for interactive
+programming based on
+`REPL <https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop>`_.
+If we take into account that microcontrollers usually provide wide range of
+different I/O peripherals, REPL executing instructions on microcontroller can
+represent exploratory environment for testing interaction with low level
+peripheries not usually available on more powerful general purpose computing
+platforms. Although, this kind of environment can be provided by splitting
+functionality between microcontroller and more powerful general purpose
+computer which communicates with microcontroller, that approach relies on
+availability of general purpose computer. By executing interpreter as a whole
+on microcontroller, more self sufficient interactive platform is available in
+form of a single microcontroller. Interaction with interpreter running on
+microcontroller can be based on full duplex
+`UART <https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter>`_
+communication. In this way, wide range of simple terminals can be used (even
+those utilizing other microcontrollers).
+
+Because implementation of Lisp interpreter in C programming language doesn't
+require a lot of dependencies specific to single target platform,
+implementation can be based on thin abstraction layer that will provide
+necessary interaction with host platform. This enables running implementation
+based on same source code on different platforms, including more powerful
+general purpose computers. Implementation targeting POSIX systems enables
+easier testing and provides more developer friendly environment for exploring
+interpreter characteristics. Usability of single code base for different
+execution environments impacts some of API design decisions. Most notably,
+interpreter uses memory locations referenced by additional pointer indirection
+instead of statically allocated locations. This approach induces some penalties
+in form of execution speed but at the same time provides API more suitable
+for interaction with
+`foreign function interface <https://en.wikipedia.org/wiki/Foreign_function_interface>`_
+and usage of multiple independent interpreter instances as part of single POSIX
+process.
+
+Existence of this project is mostly motivated with educational and research
+reasons. Therefore, significant emphasis is given on this documentation as
+integrated part of project. Rest of documentation tries to explain and document
+implementation in gradual bottom-up approach. It is advised to read this
+documentation sequential in order because each chapter depends on explanations
+available in previous chapters. Source code is organized to closely follow
+documentation structure and its listing is available as part of each associated
+chapter. Reader's knowledge of C programming language and related
+platform/memory model is assumed.
diff --git a/docs/main.rst b/docs/main.rst
new file mode 100644
index 0000000..075cc1c
--- /dev/null
+++ b/docs/main.rst
@@ -0,0 +1,15 @@
+Main
+====
+
+Implementation of `main` function is responsible for initialization of
+required structures and calling REPL function.
+
+
+Source code
+-----------
+
+main.c
+''''''
+
+.. literalinclude:: ../src_c/main.c
+ :language: c
diff --git a/docs/mem.rst b/docs/mem.rst
new file mode 100644
index 0000000..20353db
--- /dev/null
+++ b/docs/mem.rst
@@ -0,0 +1,126 @@
+Memory management
+=================
+
+Functions declared in `mem.h` provide interface for usage of dynamically
+allocated data. All other modules interact with dynamic data only through
+these functions.
+
+
+Memory layout
+-------------
+
+State of memory management is represented with structure `lsp_mem_t`. It
+contains continuous memory block of platform specific word count.
+Addresses used for referencing each data correspond to this memory block
+starting word index. Because addresses are represented with 14bit unsigned
+integer values, usable memory for allocation of dynamic data is limited to
+16384 words (32768 bytes). In case of memory constrained systems, this
+size is even smaller.
+
+
+Data allocation
+---------------
+
+Each word (event those which are not single data starting words) have
+single bit dedicated for memory management usage. This most significant bit
+represent words current usage state where ``0`` represents used word (word is
+used for representing data content) and ``1`` represent unused word (word is
+available for allocation of new data instance).
+
+During allocation of new data, all available words are searched for
+continuous block of unused words which could be used to represent newly
+allocated data content. If such word block could not be found, garbage
+collection procedure is triggered, after which search for free block is
+repeated. If search is not successful for the second time, allocation
+of new data fails. If search is successful, address of newly allocated data
+is remembered and used as starting address for future allocation searches.
+
+All `lsp_mem_create_*` functions, used for data allocation, add allocated
+data to list of accessible root data. Management of accessible data is
+possible with `lsp_mem_inc_ref` and `lsp_mem_dec_ref` which increase and
+decrease references to root data. Once data is not part of root list or is not
+referenced by other data which are part of root list, it is considered
+not accessible and can be reclaimed by garbage collector.
+
+During memory initialization, often used data instances are preallocated and
+available as part of `lsp_mem_t` structure (`nil`, `zero`, `one`, `quote`,
+`quasiquote`, `unquote` and `unquote_splicing`).
+
+
+Garbage collector
+-----------------
+
+Garbage collector is based on variant of simple mark and sweep design.
+Initially, all memory words are marked as free. Once words are used for data
+representation, used words are immediately marked as non free. After
+repeated allocation of new data instances, pool of available free words
+is depleted and garbage procedure is started.
+
+As first step of garbage collection, all memory words are marked as free.
+Then, words used for representation of root list are marked as non free by
+usage of recursive function which, together with immediately used words, marks
+all other referenced data words. Data types which reference other data are
+`pair`, `function` and `syntax`. After all data directly and indirectly
+referenced by root list is marked as used, garbage collection finishes
+(all other words remain marked as unused).
+
+
+Data usage
+----------
+
+Together with data allocation function, memory management functions include
+interface for manipulation and usage of allocated data. These include
+`lsp_mem_is_*` function for data type assertion and `lsp_mem_get_*` functions
+for data content retrieval. This functions provide thin wrapper for `cell.h`
+functions with mapping of data addresses to data words.
+
+All data types, except for `string` and `pair` are considered immutable -
+data content is initialized during allocation and is not mutated afterwards.
+In case of `string` and `pair` data, `lsp_mem_set_*` functions enable
+in place modification of data instance content. In case of `string` data,
+only content of preallocated size can be modified (size of string can not
+change after allocation).
+
+All other parts of interpreter use only `lsp_mem_*` wrappers instead of
+direct `lsp_cell_*` function usage.
+
+
+Symbol reusability
+------------------
+
+Because symbols are immutable, memory usage optimization can be done during
+symbol allocation. Each time new symbol should be allocated, content of
+all memory words is searched for already allocated symbol with same content.
+If such instance is found, reference to already existing symbol is returned
+together with incrementing this reference in root list. This optimization
+comes with cost of additional memory search during each symbol allocation.
+
+
+Ownership conventions
+---------------------
+
+Since data availability is controlled with usage create function and
+reference incrementation/decrementation, convention for memory ownership is
+required. In case of this project, if not explicitly specified otherwise,
+caller of function is owner of all input arguments and remains their owner
+after function execution finished (function temporary borrows ownership
+of input arguments). If function returns data, ownership of returned data
+is passed to function caller. It is responsibility of function caller to
+release data returned as result of function execution.
+
+
+Source code
+-----------
+
+mem.h
+'''''
+
+.. literalinclude:: ../src_c/mem.h
+ :language: c
+
+
+mem.c
+'''''
+
+.. literalinclude:: ../src_c/mem.c
+ :language: c
diff --git a/docs/read.rst b/docs/read.rst
new file mode 100644
index 0000000..9d973f4
--- /dev/null
+++ b/docs/read.rst
@@ -0,0 +1,160 @@
+Data reader
+===========
+
+Data reader is responsible for reading from input stream and creating
+data instances represented with input character sequences. In case of
+Lisp, interpreter instructions are represented with data structures. Therefor,
+parser for string representation of data is also parser form programming
+language itself. Some of data types don't have string representation
+(`builtin function`, `builtin syntax`, `function` and `syntax`) and
+can not be produced by reader.
+
+Character ``;`` represents beginning of comment which spawns to the end of line
+(until ``\n`` character is read). White space characters (`` ``, ``\n``,
+``\r``, ``\t``) and comments are ignored by reader. Only significance of
+white space characters is as a data delimiter.
+
+
+Number
+------
+
+Numbers as encoded as sequence of decimal characters (``0`` to ``9``).
+Start of number is detected by starting decimal character.
+
+Example of valid number representations::
+
+ 0
+ 1
+ 42
+
+
+String
+------
+
+String is represented with arbitrary number of characters enclosed between
+``"`` (maximum string length is 2047). ``\`` is used as escape character
+in representation of:
+
+ ``\n``, ``\r``, ``\t``, ``\\``, ``\"``
+
+Start of string is detected by ``"`` character.
+
+Example of valid string representations::
+
+ ""
+ "abc"
+ "\""
+
+
+Symbol
+------
+
+Symbol is represented with arbitrary sequence of characters. Sequence can not
+include white space characters, ``(`` or ``)``. Sequence can not start with
+decimal number character, ``"``, ``'``, ````` or ``,``.
+
+Example of valid symbol representations::
+
+ abc
+ -
+ +=/@
+ symbol-123
+
+
+Pair/list
+---------
+
+Pair is represented with form::
+
+ (<first> . <second>)
+
+where ``<first>`` and ``<second>`` are arbitrary data representations
+(including other pars/lists).
+
+Nested pairs of form::
+
+ (<el_1> . (<el_2> . (.... . <el_n>)))
+
+where ``<el_1>``, ``<el_2>``, ..., ``<el_n>`` is arbitrary data, can be
+written as::
+
+ (<el_1> <el_2> ... . <el_n>)
+
+Special case of pair is empty list represented as::
+
+ ()
+
+which can be recursively specified as::
+
+ (() . ())
+
+Sequence of nested pairs with empty list as last element is called list::
+
+ (<el_1> . (<el_2> . (.... . (<el_n> . ()))))
+
+and can be written as::
+
+ (<el_1> <el_2> ... <el_n>)
+
+Example of valid pairs/lists representations::
+
+ ()
+ (1 . 2)
+ (a . (1 . "b"))
+ (1 2 3 4)
+ ("abc" abc . 123)
+
+
+Reader macros
+-------------
+
+To enable more concise representation of complex forms, reader recognize
+few builtin reader macros. These do not introduce new data types and are used
+only as more convenient representation of other standard data forms:
+
+ * quote
+
+ Recognized by starting character ``'``. Forms::
+
+ '<data>
+
+ are equivalent to::
+
+ (quote <data>)
+
+ * quasiquote
+
+ Recognized by starting character `````. Forms::
+
+ `<data>
+
+ are equivalent to::
+
+ (quasiquote <data>)
+
+ * unquote
+
+ Recognized by starting character ``,``. Forms::
+
+ ,<data>
+
+ are equivalent to::
+
+ (unquote <data>)
+
+
+Source code
+-----------
+
+read.h
+''''''
+
+.. literalinclude:: ../src_c/read.h
+ :language: c
+
+
+read.c
+''''''
+
+.. literalinclude:: ../src_c/read.c
+ :language: c
diff --git a/docs/repl.rst b/docs/repl.rst
new file mode 100644
index 0000000..9046781
--- /dev/null
+++ b/docs/repl.rst
@@ -0,0 +1,40 @@
+REPL
+====
+
+REPL, as it's name suggest, is function implementing endless loop with
+following actions:
+
+ * read
+
+ First step is reading data from input stream.
+
+ * evaluate
+
+ Data that was read from input stream represent expression that
+ should be evaluated by interpreter.
+
+ * print
+
+ Once evaluation finishes, result of evaluation is written to
+ output stream. In case resulting data is ``()``, print step is
+ skipped.
+
+This loop is stopped only in case closing of input or output stream is
+detected.
+
+
+Source code
+-----------
+
+repl.h
+''''''
+
+.. literalinclude:: ../src_c/repl.h
+ :language: c
+
+
+repl.c
+''''''
+
+.. literalinclude:: ../src_c/repl.c
+ :language: c
diff --git a/docs/static/custom.css b/docs/static/custom.css
new file mode 100644
index 0000000..dc6a004
--- /dev/null
+++ b/docs/static/custom.css
@@ -0,0 +1,22 @@
+
+body > div > nav > div > div.wy-side-nav-search > div.version {
+ margin-bottom: 0px;
+}
+
+dl {
+ padding: 5px;
+}
+
+blockquote {
+ border-left: none;
+ font-style: normal;
+ padding: 0 0 0.5rem 0;
+ margin-block-start: 0.5em;
+ margin-block-end: 0.5em;
+ background: transparent;
+}
+
+ol, ul {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+}
diff --git a/docs/status.rst b/docs/status.rst
new file mode 100644
index 0000000..8ac3628
--- /dev/null
+++ b/docs/status.rst
@@ -0,0 +1,76 @@
+Result status codes
+===================
+
+This project adopts widely used convention of returning integer encoded status
+codes as function results.
+
+Each function, that needs to notify it's execution status (success or error),
+returns ``lsp_status_t`` (alias for ``lsp_int8_t``).
+
+Available status codes are:
+
+ * ``LSP_SUCCESS``
+
+ Execution successful.
+
+ * ``LSP_EOF``
+
+ End of file encountered during reading/writing.
+
+ * ``LSP_ERR``
+
+ Generic error (unknown error).
+
+ * ``LSP_ERR_MEM``
+
+ Memory error (usually out of memory).
+
+ * ``LSP_ERR_CTX``
+
+ Context error (usually symbol resolution error).
+
+ * ``LSP_ERR_READ``
+
+ Reader error.
+
+ * ``LSP_ERR_WRITE``
+
+ Writer error.
+
+ * ``LSP_ERR_EVAL``
+
+ Evaluation error.
+
+ * ``LSP_ERR_APPLY``
+
+ Application error.
+
+ * ``LSP_ERR_ARG_COUNT``
+
+ Invalid argument count.
+
+ * ``LSP_ERR_ARG_TYPE``
+
+ Invalid argument type.
+
+ * ``LSP_ERR_ARG_VALUE``
+
+ Invalid argument value.
+
+ * ``LSP_ERR_USER``
+
+ Special status value representing beginning of user status codes.
+
+User has ability to raise user error with ``error`` builtin function. This
+error is additionally described with integer value in range [0, 126] and
+encoded as status code.
+
+
+Source code
+-----------
+
+status.h
+''''''''
+
+.. literalinclude:: ../src_c/status.h
+ :language: c
diff --git a/docs/stream.rst b/docs/stream.rst
new file mode 100644
index 0000000..7b90cec
--- /dev/null
+++ b/docs/stream.rst
@@ -0,0 +1,54 @@
+Input/output stream
+===================
+
+To enable interaction with interpreter, basic input/output stream abstraction
+is needed. This implementation uses platform specific functions defined
+by architecture abstraction layer.
+
+
+Input stream
+------------
+
+Input stream provides functionality of reading unsigned 8bit integers
+representing input characters. Implementation utilized `lsp_stream_getchar_t`
+function pointer provided during input stream initialization. Together with
+`lsp_in_stream_read`, used for reading next available input character, input
+stream contains single character buffer used for implementation of
+`lsp_in_stream_peek` functionality.
+
+
+Output stream
+-------------
+
+Output stream provides functionality regarding writing character data.
+It uses `lsp_stream_putchar_t` function pointer provided during output stream
+initialization. Available functions include:
+
+ * `lsp_out_stream_write`
+
+ Write single character to output stream.
+
+ * `lsp_out_stream_write_str`
+
+ Write null terminated character sequence.
+
+ * `lsp_out_stream_write_int`
+
+ Write string representation of signed integer.
+
+
+Source code
+-----------
+
+stream.h
+''''''''
+
+.. literalinclude:: ../src_c/stream.h
+ :language: c
+
+
+stream.c
+''''''''
+
+.. literalinclude:: ../src_c/stream.c
+ :language: c
diff --git a/docs/syntax.rst b/docs/syntax.rst
new file mode 100644
index 0000000..71bd35b
--- /dev/null
+++ b/docs/syntax.rst
@@ -0,0 +1,116 @@
+Builtin syntaxes
+================
+
+This implementation includes minimal number of builtin syntaxes. All other
+constructs should be defined as user defined syntaxes in Lisp itself.
+
+In following examples, lines starting with ``>`` represent characters
+provided to input stream. Lines without starting ``>`` character represent
+evaluation results written to output stream.
+
+Available builtin syntaxes are:
+
+ * `lambda`
+
+ Definition of new user defined function.
+
+ Examples::
+
+ > ((lambda x x) 1 2 3)
+ (1 2 3)
+
+ > ((lambda (x) x) 1)
+ 1
+
+ > ((lambda (x . y) y) 1 2 3)
+ (2 3)
+
+ * `syntax`
+
+ Definition of new user defined syntax.
+
+ Examples::
+
+ > ((syntax x x) (lambda x x) 1 2 3)
+ (1 2 3)
+
+ * `define`
+
+ Add new symbol binding to current context.
+
+ Examples::
+
+ > (define xyz 42)
+ > xyz
+ 42
+
+ * `set!`
+
+ Change previously defined context entry.
+
+ Examples::
+
+ > (define xyz 42)
+ > xyz
+ 42
+ > (set! xyz 24)
+ > xyz
+ 24
+
+ * `begin`
+
+ Evaluate multiple expressions and return result of last expression
+ evaluation.
+
+ Examples::
+
+ > (begin 1 2 3)
+ 3
+
+ * `quote`
+
+ Evaluates to provided argument.
+
+ Examples::
+
+ > (quote (1 2 3))
+ (1 2 3)
+
+ > '(3 2 1)
+ (3 2 1)
+
+ * `if`
+
+ If first argument evaluates to `thruthy` value, `if` syntax returns
+ result of second argument evaluation. If first argument evaluates to
+ `falsy` value, result of third argument evaluation is returned or
+ ``()`` if third argument is not available.
+
+ `Falsy` values are ``0``, ``()``, ``""`` and empty symbol.
+
+ `Thruthy` values are all that are not `falsy`.
+
+ Examples::
+
+ > (if 0 1 2)
+ 2
+
+ > (if "0" 1 2)
+ 1
+
+
+Source code
+-----------
+
+syntax.h
+''''''''
+
+.. literalinclude:: ../src_c/syntax.h
+ :language: c
+
+
+syntax.c
+''''''''
+
+.. literalinclude:: ../src_c/syntax.c
+ :language: c
diff --git a/docs/write.rst b/docs/write.rst
new file mode 100644
index 0000000..b90e2e1
--- /dev/null
+++ b/docs/write.rst
@@ -0,0 +1,27 @@
+Data writer
+===========
+
+Data writer enables serialization of data as string characters written
+to output stream. Same encoding rules that apply to data reader also apply to
+data writer.
+
+Additionally, data writer will provide informative representation of data
+types which can not be parsed by data reader (`builtin function`,
+`builtin syntax`, `function` and `syntax`).
+
+
+Source code
+-----------
+
+write.h
+'''''''
+
+.. literalinclude:: ../src_c/write.h
+ :language: c
+
+
+write.c
+'''''''
+
+.. literalinclude:: ../src_c/write.c
+ :language: c
diff --git a/examples/factorial.lsp b/examples/factorial.lsp
new file mode 100644
index 0000000..a539a01
--- /dev/null
+++ b/examples/factorial.lsp
@@ -0,0 +1,6 @@
+(define (factorial x)
+ (if x
+ (* x (factorial (- x 1)))
+ 1))
+
+(factorial 10)
diff --git a/format.sh b/format.sh
new file mode 100755
index 0000000..82ed3f5
--- /dev/null
+++ b/format.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname -- "$0")
+
+SRC="$(find src_c -name '*.c' -o -name '*.h') $(find test_c -name '*.c')"
+
+clang-format -style=file -i $SRC
diff --git a/src_c/apply.c b/src_c/apply.c
new file mode 100644
index 0000000..4dd8684
--- /dev/null
+++ b/src_c/apply.c
@@ -0,0 +1,140 @@
+#include "apply.h"
+#include "ctx.h"
+#include "eval.h"
+#include "function.h"
+#include "syntax.h"
+#include "write.h"
+
+
+static lsp_status_t create_ctx(lsp_env_t *e, lsp_addr_t parent_ctx,
+ lsp_addr_t arg_names, lsp_addr_t arg_values,
+ lsp_addr_t *result) {
+ lsp_status_t status = lsp_ctx_copy(e->m, parent_ctx, result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ while (arg_names != e->m->nil) {
+ if (lsp_mem_is_symbol(e->m, arg_names)) {
+ status = lsp_ctx_add(e->m, *result, arg_names, arg_values);
+ arg_values = e->m->nil;
+ break;
+ }
+
+ if (arg_values == e->m->nil) {
+ status = LSP_ERR_ARG_COUNT;
+ break;
+ }
+
+ lsp_addr_t arg_name = lsp_mem_get_pair_first(e->m, arg_names);
+ lsp_addr_t arg_value = lsp_mem_get_pair_first(e->m, arg_values);
+
+ if (!lsp_mem_is_symbol(e->m, arg_name)) {
+ status = LSP_ERR_ARG_TYPE;
+ break;
+ }
+
+ status = lsp_ctx_add(e->m, *result, arg_name, arg_value);
+ if (status != LSP_SUCCESS)
+ break;
+
+ arg_names = lsp_mem_get_pair_second(e->m, arg_names);
+ arg_values = lsp_mem_get_pair_second(e->m, arg_values);
+ }
+
+ if (status == LSP_SUCCESS && arg_values != e->m->nil)
+ status = LSP_ERR_ARG_COUNT;
+
+ if (status != LSP_SUCCESS)
+ lsp_mem_dec_ref(e->m, *result);
+
+ return status;
+}
+
+
+static lsp_status_t apply_builtin_function(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t callable,
+ lsp_addr_t args) {
+ lsp_uint16_t index = lsp_mem_get_builtin_index(e->m, callable);
+ lsp_builtin_cb_t cb = lsp_functions[index].cb;
+ return cb(e, ctx, args);
+}
+
+
+static lsp_status_t apply_builtin_syntax(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t callable, lsp_addr_t args) {
+ lsp_uint16_t index = lsp_mem_get_builtin_index(e->m, callable);
+ lsp_builtin_cb_t cb = lsp_syntaxes[index].cb;
+ return cb(e, ctx, args);
+}
+
+
+static lsp_status_t apply_function(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t callable, lsp_addr_t args) {
+ lsp_addr_t parent_ctx = lsp_mem_get_function_parent_ctx(e->m, callable);
+ lsp_addr_t arg_names = lsp_mem_get_function_args(e->m, callable);
+ lsp_addr_t body = lsp_mem_get_function_body(e->m, callable);
+
+ lsp_addr_t fn_ctx;
+ lsp_status_t status = create_ctx(e, parent_ctx, arg_names, args, &fn_ctx);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_syntax_begin(e, fn_ctx, body);
+ lsp_mem_dec_ref(e->m, fn_ctx);
+ return status;
+}
+
+
+static lsp_status_t apply_syntax(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t callable, lsp_addr_t args) {
+ lsp_addr_t parent_ctx = lsp_mem_get_syntax_parent_ctx(e->m, callable);
+ lsp_addr_t arg_names = lsp_mem_get_syntax_args(e->m, callable);
+ lsp_addr_t body = lsp_mem_get_syntax_body(e->m, callable);
+
+ lsp_addr_t syntax_ctx;
+ lsp_status_t status =
+ create_ctx(e, parent_ctx, arg_names, args, &syntax_ctx);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result = e->m->nil;
+ while (body != e->m->nil) {
+ lsp_mem_dec_ref(e->m, result);
+
+ lsp_addr_t value = lsp_mem_get_pair_first(e->m, body);
+ status = lsp_env_resolve(e, syntax_ctx, value, &result);
+ if (status != LSP_SUCCESS)
+ break;
+
+ body = lsp_mem_get_pair_second(e->m, body);
+ }
+
+ lsp_mem_dec_ref(e->m, syntax_ctx);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ // lsp_write(e->m, e->out, result);
+ // lsp_out_stream_write(e->out, '\n');
+
+ status = lsp_env_set_result_eval(e, ctx, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_apply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t callable,
+ lsp_addr_t args) {
+ if (lsp_mem_is_builtin_function(e->m, callable))
+ return apply_builtin_function(e, ctx, callable, args);
+
+ if (lsp_mem_is_builtin_syntax(e->m, callable))
+ return apply_builtin_syntax(e, ctx, callable, args);
+
+ if (lsp_mem_is_function(e->m, callable))
+ return apply_function(e, ctx, callable, args);
+
+ if (lsp_mem_is_syntax(e->m, callable))
+ return apply_syntax(e, ctx, callable, args);
+
+ return LSP_ERR_APPLY;
+}
diff --git a/src_c/apply.h b/src_c/apply.h
new file mode 100644
index 0000000..ef4e1c2
--- /dev/null
+++ b/src_c/apply.h
@@ -0,0 +1,10 @@
+#ifndef LISP16_APPLY_H
+#define LISP16_APPLY_H
+
+#include "env.h"
+
+
+lsp_status_t lsp_apply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t callable,
+ lsp_addr_t args);
+
+#endif
diff --git a/src_c/arch.h b/src_c/arch.h
new file mode 100644
index 0000000..01ae8d9
--- /dev/null
+++ b/src_c/arch.h
@@ -0,0 +1,63 @@
+#ifndef LISP16_ARCH_H
+#define LISP16_ARCH_H
+
+#define LSP_ARCH_POSIX 0
+#define LSP_ARCH_AVR8 1
+#define LSP_ARCH_STM32 2
+
+#ifndef LSP_ARCH
+#error LSP_ARCH not defined
+#endif
+
+#if LSP_ARCH == LSP_ARCH_POSIX
+
+#include <stdint.h>
+typedef _Bool lsp_bool_t;
+typedef int8_t lsp_int8_t;
+typedef int16_t lsp_int16_t;
+typedef int32_t lsp_int32_t;
+typedef uint8_t lsp_uint8_t;
+typedef uint16_t lsp_uint16_t;
+typedef uint32_t lsp_uint32_t;
+
+#elif LSP_ARCH == LSP_ARCH_AVR8
+
+#include <stdint.h>
+typedef _Bool lsp_bool_t;
+typedef int8_t lsp_int8_t;
+typedef int16_t lsp_int16_t;
+typedef int32_t lsp_int32_t;
+typedef uint8_t lsp_uint8_t;
+typedef uint16_t lsp_uint16_t;
+typedef uint32_t lsp_uint32_t;
+
+#elif LSP_ARCH == LSP_ARCH_STM32
+
+#include <stdint.h>
+typedef _Bool lsp_bool_t;
+typedef int8_t lsp_int8_t;
+typedef int16_t lsp_int16_t;
+typedef int32_t lsp_int32_t;
+typedef uint8_t lsp_uint8_t;
+typedef uint16_t lsp_uint16_t;
+typedef uint32_t lsp_uint32_t;
+
+#else
+
+#error unknown LSP_ARCH
+
+#endif
+
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#ifndef true
+#define true ((lsp_bool_t)1)
+#endif
+
+#ifndef false
+#define false ((lsp_bool_t)0)
+#endif
+
+#endif
diff --git a/src_c/arch/avr8.c b/src_c/arch/avr8.c
new file mode 100644
index 0000000..cbc454b
--- /dev/null
+++ b/src_c/arch/avr8.c
@@ -0,0 +1,63 @@
+#include "avr8.h"
+#include <avr/io.h>
+
+#define MEM_SIZE 0x01c0
+#define UART_BAUD 9600
+
+
+static lsp_int16_t avr8_getchar(lsp_in_stream_t *s) {
+ while (!(UCSR0A & (1 << 7)))
+ ;
+ return UDR0;
+}
+
+
+static lsp_int16_t avr8_putchar(lsp_out_stream_t *s, lsp_int16_t c) {
+ while (!(UCSR0A & (1 << 5)))
+ ;
+ UDR0 = c;
+}
+
+
+static lsp_uint8_t avr8_mem[sizeof(lsp_mem_t) + MEM_SIZE * sizeof(lsp_cell_t)];
+
+static lsp_in_stream_t avr8_in_stream;
+
+static lsp_out_stream_t avr8_out_stream;
+
+
+void lsp_arch_avr8_init() {
+ UBRR0 = F_CPU / 16 / UART_BAUD - 1;
+ UCSR0B |= _BV(TXEN0) | _BV(RXEN0);
+}
+
+
+lsp_mem_t *lsp_arch_avr8_create_mem() {
+ lsp_mem_t *m = (void *)avr8_mem;
+ if (lsp_mem_init(m, MEM_SIZE) != LSP_SUCCESS)
+ return NULL;
+ return m;
+}
+
+
+void lsp_arch_avr8_free_mem(lsp_mem_t *m) {}
+
+
+lsp_in_stream_t *lsp_arch_avr8_create_in_stream() {
+ lsp_in_stream_t *s = &avr8_in_stream;
+ lsp_in_stream_init(s, avr8_getchar);
+ return s;
+}
+
+
+void lsp_arch_avr8_free_in_stream(lsp_in_stream_t *s) {}
+
+
+lsp_out_stream_t *lsp_arch_avr8_create_out_stream() {
+ lsp_out_stream_t *s = &avr8_out_stream;
+ lsp_out_stream_init(s, avr8_putchar);
+ return s;
+}
+
+
+void lsp_arch_avr8_free_out_stream(lsp_out_stream_t *s) {}
diff --git a/src_c/arch/avr8.h b/src_c/arch/avr8.h
new file mode 100644
index 0000000..351ee14
--- /dev/null
+++ b/src_c/arch/avr8.h
@@ -0,0 +1,30 @@
+#ifndef LISP16_ARCH_AVR8_H
+#define LISP16_ARCH_AVR8_H
+
+#include "../mem.h"
+#include "../stream.h"
+
+#define LSP_ARCH_INIT lsp_arch_avr8_init
+
+#define LSP_ARCH_CREATE_MEM lsp_arch_avr8_create_mem
+#define LSP_ARCH_FREE_MEM lsp_arch_avr8_free_mem
+
+#define LSP_ARCH_CREATE_IN_STREAM lsp_arch_avr8_create_in_stream
+#define LSP_ARCH_FREE_IN_STREAM lsp_arch_avr8_free_in_stream
+
+#define LSP_ARCH_CREATE_OUT_STREAM lsp_arch_avr8_create_out_stream
+#define LSP_ARCH_FREE_OUT_STREAM lsp_arch_avr8_free_out_stream
+
+
+void lsp_arch_avr8_init();
+
+lsp_mem_t *lsp_arch_avr8_create_mem();
+void lsp_arch_avr8_free_mem(lsp_mem_t *m);
+
+lsp_in_stream_t *lsp_arch_avr8_create_in_stream();
+void lsp_arch_avr8_free_in_stream(lsp_in_stream_t *s);
+
+lsp_out_stream_t *lsp_arch_avr8_create_out_stream();
+void lsp_arch_avr8_free_out_stream(lsp_out_stream_t *s);
+
+#endif
diff --git a/src_c/arch/posix.c b/src_c/arch/posix.c
new file mode 100644
index 0000000..19e33a1
--- /dev/null
+++ b/src_c/arch/posix.c
@@ -0,0 +1,54 @@
+#include "posix.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MEM_SIZE 0x4000
+
+
+static lsp_int16_t posix_getchar(lsp_in_stream_t *s) {
+ int c = getchar();
+ if (c == EOF)
+ return LSP_EOF;
+ return c;
+}
+
+
+static lsp_int16_t posix_putchar(lsp_out_stream_t *s, lsp_int16_t c) {
+ return putchar(c);
+}
+
+
+void lsp_arch_posix_init() {}
+
+
+lsp_mem_t *lsp_arch_posix_create_mem() {
+ lsp_mem_t *m = malloc(sizeof(lsp_mem_t) + sizeof(lsp_cell_t) * MEM_SIZE);
+ if (lsp_mem_init(m, MEM_SIZE) != LSP_SUCCESS) {
+ free(m);
+ return NULL;
+ }
+ return m;
+}
+
+
+void lsp_arch_posix_free_mem(lsp_mem_t *m) { free(m); }
+
+
+lsp_in_stream_t *lsp_arch_posix_create_in_stream() {
+ lsp_in_stream_t *s = malloc(sizeof(lsp_in_stream_t));
+ lsp_in_stream_init(s, posix_getchar);
+ return s;
+}
+
+
+void lsp_arch_posix_free_in_stream(lsp_in_stream_t *s) { free(s); }
+
+
+lsp_out_stream_t *lsp_arch_posix_create_out_stream() {
+ lsp_out_stream_t *s = malloc(sizeof(lsp_out_stream_t));
+ lsp_out_stream_init(s, posix_putchar);
+ return s;
+}
+
+
+void lsp_arch_posix_free_out_stream(lsp_out_stream_t *s) { free(s); }
diff --git a/src_c/arch/posix.h b/src_c/arch/posix.h
new file mode 100644
index 0000000..5716c0a
--- /dev/null
+++ b/src_c/arch/posix.h
@@ -0,0 +1,30 @@
+#ifndef LISP16_ARCH_POSIX_H
+#define LISP16_ARCH_POSIX_H
+
+#include "../mem.h"
+#include "../stream.h"
+
+#define LSP_ARCH_INIT lsp_arch_posix_init
+
+#define LSP_ARCH_CREATE_MEM lsp_arch_posix_create_mem
+#define LSP_ARCH_FREE_MEM lsp_arch_posix_free_mem
+
+#define LSP_ARCH_CREATE_IN_STREAM lsp_arch_posix_create_in_stream
+#define LSP_ARCH_FREE_IN_STREAM lsp_arch_posix_free_in_stream
+
+#define LSP_ARCH_CREATE_OUT_STREAM lsp_arch_posix_create_out_stream
+#define LSP_ARCH_FREE_OUT_STREAM lsp_arch_posix_free_out_stream
+
+
+void lsp_arch_posix_init();
+
+lsp_mem_t *lsp_arch_posix_create_mem();
+void lsp_arch_posix_free_mem(lsp_mem_t *m);
+
+lsp_in_stream_t *lsp_arch_posix_create_in_stream();
+void lsp_arch_posix_free_in_stream(lsp_in_stream_t *s);
+
+lsp_out_stream_t *lsp_arch_posix_create_out_stream();
+void lsp_arch_posix_free_out_stream(lsp_out_stream_t *s);
+
+#endif
diff --git a/src_c/buff.c b/src_c/buff.c
new file mode 100644
index 0000000..e22eba8
--- /dev/null
+++ b/src_c/buff.c
@@ -0,0 +1,61 @@
+#include "buff.h"
+
+
+static lsp_status_t update_str(lsp_buff_t *b) {
+ lsp_uint16_t value_len =
+ ((b->value == b->m->nil) ? 0 : lsp_mem_get_string_len(b->m, b->value));
+
+ lsp_addr_t value;
+ lsp_status_t status =
+ lsp_mem_create_string(b->m, value_len + b->buff_len, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ for (lsp_uint16_t i = 0; i < value_len; ++i)
+ lsp_mem_set_string_data(b->m, value, i,
+ lsp_mem_get_string_data(b->m, b->value, i));
+
+ for (lsp_uint16_t i = 0; i < b->buff_len; ++i)
+ lsp_mem_set_string_data(b->m, value, i + value_len, b->buff[i]);
+
+ lsp_mem_dec_ref(b->m, b->value);
+ b->value = value;
+ b->buff_len = 0;
+ return LSP_SUCCESS;
+}
+
+
+void lsp_buff_init(lsp_buff_t *b, lsp_mem_t *m) {
+ b->m = m;
+ b->value = m->nil;
+ b->buff_len = 0;
+}
+
+
+lsp_status_t lsp_buff_push(lsp_buff_t *b, lsp_uint8_t c) {
+ if (b->buff_len + 1 >= LSP_BUFF_SIZE) {
+ lsp_status_t status = update_str(b);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ b->buff[b->buff_len++] = c;
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_buff_pop(lsp_buff_t *b, lsp_addr_t *value) {
+ lsp_status_t status = update_str(b);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ *value = b->value;
+ b->value = b->m->nil;
+ return LSP_SUCCESS;
+}
+
+
+void lsp_buff_clear(lsp_buff_t *b) {
+ lsp_mem_dec_ref(b->m, b->value);
+ b->value = b->m->nil;
+}
diff --git a/src_c/buff.h b/src_c/buff.h
new file mode 100644
index 0000000..d17694c
--- /dev/null
+++ b/src_c/buff.h
@@ -0,0 +1,23 @@
+#ifndef LISP16_BUFF_H
+#define LISP16_BUFF_H
+
+#include "mem.h"
+#include "status.h"
+
+#define LSP_BUFF_SIZE 32
+
+
+typedef struct {
+ lsp_mem_t *m;
+ lsp_addr_t value;
+ lsp_uint8_t buff[LSP_BUFF_SIZE];
+ lsp_uint8_t buff_len;
+} lsp_buff_t;
+
+
+void lsp_buff_init(lsp_buff_t *b, lsp_mem_t *m);
+lsp_status_t lsp_buff_push(lsp_buff_t *b, lsp_uint8_t c);
+lsp_status_t lsp_buff_pop(lsp_buff_t *b, lsp_addr_t *value);
+void lsp_buff_clear(lsp_buff_t *b);
+
+#endif
diff --git a/src_c/builtin.c b/src_c/builtin.c
new file mode 100644
index 0000000..92e1fa8
--- /dev/null
+++ b/src_c/builtin.c
@@ -0,0 +1,65 @@
+#include "builtin.h"
+
+
+lsp_status_t lsp_builtin_get_args_1(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1) {
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg1 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args != m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_builtin_get_args_2(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1, lsp_addr_t *arg2) {
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg1 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg2 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args != m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_builtin_get_args_3(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1, lsp_addr_t *arg2,
+ lsp_addr_t *arg3) {
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg1 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg2 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args == m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ *arg3 = lsp_mem_get_pair_first(m, args);
+
+ args = lsp_mem_get_pair_second(m, args);
+ if (args != m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ return LSP_SUCCESS;
+}
diff --git a/src_c/builtin.h b/src_c/builtin.h
new file mode 100644
index 0000000..af9fb51
--- /dev/null
+++ b/src_c/builtin.h
@@ -0,0 +1,24 @@
+#ifndef LISP16_BUILTIN_H
+#define LISP16_BUILTIN_H
+
+#include "env.h"
+
+
+typedef lsp_status_t (*lsp_builtin_cb_t)(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+
+typedef struct {
+ char *name;
+ lsp_builtin_cb_t cb;
+} lsp_builtin_entry_t;
+
+
+lsp_status_t lsp_builtin_get_args_1(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1);
+lsp_status_t lsp_builtin_get_args_2(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1, lsp_addr_t *arg2);
+lsp_status_t lsp_builtin_get_args_3(lsp_mem_t *m, lsp_addr_t args,
+ lsp_addr_t *arg1, lsp_addr_t *arg2,
+ lsp_addr_t *arg3);
+
+#endif
diff --git a/src_c/cell.c b/src_c/cell.c
new file mode 100644
index 0000000..66da137
--- /dev/null
+++ b/src_c/cell.c
@@ -0,0 +1,50 @@
+#include "cell.h"
+
+
+lsp_uint16_t lsp_cell_get_size(lsp_cell_t *c) {
+ if (lsp_cell_is_number(c)) {
+ if (!(c[0] & 0x2000))
+ return 1;
+
+ for (lsp_uint8_t i = 1; true; ++i) {
+ if (!(c[i] & 0x4000))
+ return i + 1;
+ }
+
+ } else if (lsp_cell_is_pair(c)) {
+ return 2;
+
+ } else if (lsp_cell_is_string_or_symbol(c)) {
+ return lsp_cell_get_string_symbol_size(*c & 0x07ff);
+
+ } else if (lsp_cell_is_builtin(c)) {
+ return 1;
+
+ } else if (lsp_cell_is_function_or_syntax(c)) {
+ return 4;
+ }
+
+ return 0;
+}
+
+
+lsp_uint16_t lsp_cell_get_number_size(lsp_int32_t value) {
+ lsp_bool_t msb = ((value & 0x1000) ? true : false);
+ value >>= 13;
+ if (value == 0 || value == -1)
+ return 1 + ((msb && !value) ? 1 : 0);
+
+ for (lsp_uint8_t i = 0; 1; ++i) {
+ msb = ((value & 0x4000) ? 1 : 0);
+ value >>= 15;
+ if (value == 0 || value == -1)
+ return i + 2 + ((msb && !value) ? 1 : 0);
+ }
+
+ return 0;
+}
+
+
+lsp_uint16_t lsp_cell_get_string_symbol_size(lsp_uint16_t len) {
+ return (((lsp_uint32_t)len << 3) / 15) + ((len % 15) ? 1 : 0) + 1;
+}
diff --git a/src_c/cell.h b/src_c/cell.h
new file mode 100644
index 0000000..b8ed7c0
--- /dev/null
+++ b/src_c/cell.h
@@ -0,0 +1,215 @@
+#ifndef LISP16_CELL_H
+#define LISP16_CELL_H
+
+#include "arch.h"
+
+
+typedef lsp_uint16_t lsp_cell_t;
+typedef lsp_uint16_t lsp_addr_t; // 14 least significant bits used
+
+
+lsp_uint16_t lsp_cell_get_size(lsp_cell_t *c);
+lsp_uint16_t lsp_cell_get_number_size(lsp_int32_t value);
+lsp_uint16_t lsp_cell_get_string_symbol_size(lsp_uint16_t len);
+
+
+static inline lsp_bool_t lsp_cell_is_number(lsp_cell_t *c) {
+ return (*c & 0x4000) == 0x0000;
+}
+
+static inline lsp_bool_t lsp_cell_is_pair(lsp_cell_t *c) {
+ return (*c & 0x6000) == 0x4000;
+}
+
+static inline lsp_bool_t lsp_cell_is_string(lsp_cell_t *c) {
+ return (*c & 0x7800) == 0x6000;
+}
+
+static inline lsp_bool_t lsp_cell_is_symbol(lsp_cell_t *c) {
+ return (*c & 0x7800) == 0x6800;
+}
+
+static inline lsp_bool_t lsp_cell_is_builtin_function(lsp_cell_t *c) {
+ return (*c & 0x7c00) == 0x7000;
+}
+
+static inline lsp_bool_t lsp_cell_is_builtin_syntax(lsp_cell_t *c) {
+ return (*c & 0x7c00) == 0x7400;
+}
+
+static inline lsp_bool_t lsp_cell_is_function(lsp_cell_t *c) {
+ return (*c & 0x7c00) == 0x7800;
+}
+
+static inline lsp_bool_t lsp_cell_is_syntax(lsp_cell_t *c) {
+ return (*c & 0x7c00) == 0x7c00;
+}
+
+static inline lsp_bool_t lsp_cell_is_string_or_symbol(lsp_cell_t *c) {
+ return (*c & 0x7000) == 0x6000;
+}
+
+static inline lsp_bool_t lsp_cell_is_builtin(lsp_cell_t *c) {
+ return (*c & 0x7800) == 0x7000;
+}
+
+static inline lsp_bool_t lsp_cell_is_function_or_syntax(lsp_cell_t *c) {
+ return (*c & 0x7800) == 0x7800;
+}
+
+
+static inline void lsp_cell_set_number(lsp_cell_t *c, lsp_int32_t value) {
+ lsp_uint8_t size = lsp_cell_get_number_size(value);
+ for (lsp_uint8_t i = 0; i < size; ++i) {
+ lsp_uint8_t shift = (size - i - 1) * 14;
+ c[i] = (value >> shift) & (i ? 0x3fff : 0x1fff);
+ if (i != size - 1)
+ c[i] |= (i ? 0x4000 : 0x2000);
+ }
+}
+
+static inline void lsp_cell_set_pair(lsp_cell_t *c, lsp_addr_t first,
+ lsp_addr_t second) {
+ c[0] = 0x4000 | ((first >> 1) & 0x1fff);
+ c[1] = ((first & 1) ? 0x4000 : 0) | (second & 0x3fff);
+}
+
+static inline void lsp_cell_set_string(lsp_cell_t *c, lsp_uint16_t data_len) {
+ lsp_uint16_t size = lsp_cell_get_string_symbol_size(data_len);
+ c[0] = 0x6000 | (data_len & 0x07ff);
+ for (lsp_uint16_t i = 1; i < size; ++i)
+ c[i] = 0;
+}
+
+static inline void lsp_cell_set_string_data(lsp_cell_t *c, lsp_uint16_t i,
+ lsp_uint8_t data_i) {
+ lsp_uint32_t bit_count = (lsp_uint32_t)i << 3;
+ lsp_uint16_t start_cell = bit_count / 15;
+ lsp_uint8_t bit_shift = bit_count % 15;
+ lsp_uint16_t mask = 0x7f80 >> bit_shift;
+ if (bit_shift < 8) {
+ c[1 + start_cell] = (c[1 + start_cell] & ~mask) |
+ ((lsp_uint16_t)data_i << (7 - bit_shift));
+ } else {
+ c[1 + start_cell] =
+ (c[1 + start_cell] & ~mask) | (data_i >> (bit_shift - 7));
+ bit_shift = 22 - bit_shift;
+ mask = (0xffff << bit_shift) & 0x7fff;
+ c[2 + start_cell] = (c[2 + start_cell] & ~mask) |
+ (((lsp_uint16_t)data_i << bit_shift) & mask);
+ }
+}
+
+static inline void lsp_cell_set_symbol(lsp_cell_t *c, lsp_uint16_t name_len) {
+ lsp_uint16_t size = lsp_cell_get_string_symbol_size(name_len);
+ c[0] = 0x6800 | (name_len & 0x07ff);
+ for (lsp_uint16_t i = 1; i < size; ++i)
+ c[i] = 0;
+}
+
+static inline void lsp_cell_set_symbol_name(lsp_cell_t *c, lsp_uint16_t i,
+ lsp_uint8_t name_i) {
+ lsp_cell_set_string_data(c, i, name_i);
+}
+
+static inline void lsp_cell_set_builtin_function(lsp_cell_t *c,
+ lsp_uint16_t index) {
+ *c = 0x7000 | (index & 0x03ff);
+}
+
+static inline void lsp_cell_set_builtin_syntax(lsp_cell_t *c,
+ lsp_uint16_t index) {
+ *c = 0x7400 | (index & 0x03ff);
+}
+
+static inline void lsp_cell_set_function(lsp_cell_t *c, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body) {
+ c[0] = 0x7800;
+ c[1] = parent_ctx & 0x3fff;
+ c[2] = args & 0x3fff;
+ c[3] = body & 0x3fff;
+}
+
+static inline void lsp_cell_set_syntax(lsp_cell_t *c, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body) {
+ c[0] = 0x7c00;
+ c[1] = parent_ctx & 0x3fff;
+ c[2] = args & 0x3fff;
+ c[3] = body & 0x3fff;
+}
+
+
+static inline lsp_int32_t lsp_cell_get_number(lsp_cell_t *c) {
+ lsp_int32_t v = ((c[0] & 0x1000) ? -1 : 0);
+ v = (v << 12) | (c[0] & 0x0fff);
+ if (!(c[0] & 0x2000))
+ return v;
+ for (lsp_uint8_t i = 1; 1; ++i) {
+ v = (v << 14) | (c[1] & 0x3fff);
+ if (!(c[i] & 0x4000))
+ return v;
+ }
+}
+
+static inline lsp_addr_t lsp_cell_get_pair_first(lsp_cell_t *c) {
+ return ((c[0] & 0x1fff) << 1) + ((c[1] & 0x4000) ? 1 : 0);
+}
+
+static inline lsp_addr_t lsp_cell_get_pair_second(lsp_cell_t *c) {
+ return c[1] & 0x3fff;
+}
+
+static inline lsp_uint16_t lsp_cell_get_string_len(lsp_cell_t *c) {
+ return *c & 0x07ff;
+}
+
+static inline lsp_uint8_t lsp_cell_get_string_data(lsp_cell_t *c,
+ lsp_uint16_t i) {
+ lsp_uint32_t bit_count = (lsp_uint32_t)i << 3;
+ lsp_uint16_t start_cell = bit_count / 15;
+ lsp_uint8_t start_bit = 14 - (bit_count % 15);
+ if (start_bit >= 7)
+ return (c[1 + start_cell] >> (start_bit - 7)) & 0xff;
+ return ((c[1 + start_cell] << (7 - start_bit)) |
+ ((c[2 + start_cell] & 0x7fff) >> (8 + start_bit))) &
+ 0xff;
+}
+
+static inline lsp_uint16_t lsp_cell_get_symbol_len(lsp_cell_t *c) {
+ return lsp_cell_get_string_len(c);
+}
+
+static inline lsp_uint8_t lsp_cell_get_symbol_name(lsp_cell_t *c,
+ lsp_uint16_t i) {
+ return lsp_cell_get_string_data(c, i);
+}
+
+static inline lsp_uint16_t lsp_cell_get_builtin_index(lsp_cell_t *c) {
+ return *c & 0x03ff;
+}
+
+static inline lsp_addr_t lsp_cell_get_function_parent_ctx(lsp_cell_t *c) {
+ return c[1] & 0x3fff;
+}
+
+static inline lsp_addr_t lsp_cell_get_function_args(lsp_cell_t *c) {
+ return c[2] & 0x3fff;
+}
+
+static inline lsp_addr_t lsp_cell_get_function_body(lsp_cell_t *c) {
+ return c[3] & 0x3fff;
+}
+
+static inline lsp_addr_t lsp_cell_get_syntax_parent_ctx(lsp_cell_t *c) {
+ return lsp_cell_get_function_parent_ctx(c);
+}
+
+static inline lsp_addr_t lsp_cell_get_syntax_args(lsp_cell_t *c) {
+ return lsp_cell_get_function_args(c);
+}
+
+static inline lsp_addr_t lsp_cell_get_syntax_body(lsp_cell_t *c) {
+ return lsp_cell_get_function_body(c);
+}
+
+#endif
diff --git a/src_c/ctx.c b/src_c/ctx.c
new file mode 100644
index 0000000..693ea53
--- /dev/null
+++ b/src_c/ctx.c
@@ -0,0 +1,231 @@
+#include "ctx.h"
+#include "function.h"
+#include "syntax.h"
+
+
+static lsp_bool_t contains_symbol(lsp_mem_t *m, lsp_addr_t ctx,
+ lsp_addr_t symbol) {
+ while (ctx != m->nil) {
+ lsp_addr_t entry = lsp_mem_get_pair_first(m, ctx);
+ lsp_addr_t entry_symbol = lsp_mem_get_pair_first(m, entry);
+
+ if (lsp_mem_eq(m, entry_symbol, symbol))
+ return true;
+
+ ctx = lsp_mem_get_pair_second(m, ctx);
+ }
+
+ return false;
+}
+
+
+static lsp_status_t remove_symbol(lsp_mem_t *m, lsp_addr_t ctx,
+ lsp_addr_t symbol) {
+ lsp_addr_t list = lsp_mem_get_pair_first(m, ctx);
+
+ lsp_addr_t result = m->nil;
+ lsp_addr_t result_last = m->nil;
+ lsp_status_t status = LSP_SUCCESS;
+
+ while (list != m->nil) {
+ lsp_addr_t entry = lsp_mem_get_pair_first(m, list);
+ lsp_addr_t entry_symbol = lsp_mem_get_pair_first(m, entry);
+ list = lsp_mem_get_pair_second(m, list);
+
+ if (lsp_mem_eq(m, entry_symbol, symbol)) {
+ if (result == m->nil) {
+ result = list;
+ status = lsp_mem_inc_ref(m, list);
+
+ } else {
+ lsp_mem_set_pair_second(m, result_last, list);
+ }
+
+ break;
+ }
+
+ lsp_addr_t new_result_last;
+ status = lsp_mem_create_pair(m, entry, m->nil, &new_result_last);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (result == m->nil) {
+ result = new_result_last;
+
+ } else {
+ lsp_mem_set_pair_second(m, result_last, new_result_last);
+ lsp_mem_dec_ref(m, new_result_last);
+ }
+
+ result_last = new_result_last;
+ }
+
+ if (status == LSP_SUCCESS)
+ lsp_mem_set_pair_first(m, ctx, result);
+
+ lsp_mem_dec_ref(m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_ctx_create(lsp_mem_t *m, lsp_addr_t *ctx) {
+ lsp_addr_t list = m->nil;
+ lsp_addr_t list_last = m->nil;
+ lsp_status_t status = LSP_SUCCESS;
+
+ for (uint8_t i = 0; status == LSP_SUCCESS && lsp_syntaxes[i].name; ++i) {
+ lsp_addr_t symbol;
+ status =
+ lsp_mem_create_symbol_from_char(m, lsp_syntaxes[i].name, &symbol);
+ if (status != LSP_SUCCESS)
+ break;
+
+ lsp_addr_t value;
+ status = lsp_mem_create_builtin_syntax(m, i, &value);
+ if (status != LSP_SUCCESS) {
+ lsp_mem_dec_ref(m, symbol);
+ break;
+ }
+
+ lsp_addr_t entry;
+ status = lsp_mem_create_pair(m, symbol, value, &entry);
+ lsp_mem_dec_ref(m, symbol);
+ lsp_mem_dec_ref(m, value);
+ if (status != LSP_SUCCESS)
+ break;
+
+ lsp_addr_t new_list_last;
+ status = lsp_mem_create_pair(m, entry, m->nil, &new_list_last);
+ lsp_mem_dec_ref(m, entry);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (list == m->nil) {
+ list = new_list_last;
+
+ } else {
+ lsp_mem_set_pair_second(m, list_last, new_list_last);
+ lsp_mem_dec_ref(m, new_list_last);
+ }
+
+ list_last = new_list_last;
+ }
+
+ for (uint8_t i = 0; status == LSP_SUCCESS && lsp_functions[i].name; ++i) {
+ lsp_addr_t symbol;
+ status =
+ lsp_mem_create_symbol_from_char(m, lsp_functions[i].name, &symbol);
+ if (status != LSP_SUCCESS)
+ break;
+
+ lsp_addr_t value;
+ status = lsp_mem_create_builtin_function(m, i, &value);
+ if (status != LSP_SUCCESS) {
+ lsp_mem_dec_ref(m, symbol);
+ break;
+ }
+
+ lsp_addr_t entry;
+ status = lsp_mem_create_pair(m, symbol, value, &entry);
+ lsp_mem_dec_ref(m, symbol);
+ lsp_mem_dec_ref(m, value);
+ if (status != LSP_SUCCESS)
+ break;
+
+ lsp_addr_t new_list_last;
+ status = lsp_mem_create_pair(m, entry, m->nil, &new_list_last);
+ lsp_mem_dec_ref(m, entry);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (list == m->nil) {
+ list = new_list_last;
+
+ } else {
+ lsp_mem_set_pair_second(m, list_last, new_list_last);
+ lsp_mem_dec_ref(m, new_list_last);
+ }
+
+ list_last = new_list_last;
+ }
+
+ if (status == LSP_SUCCESS)
+ status = lsp_mem_create_pair(m, list, m->nil, ctx);
+
+ lsp_mem_dec_ref(m, list);
+ return status;
+}
+
+
+lsp_status_t lsp_ctx_copy(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t *result) {
+ lsp_addr_t list = lsp_mem_get_pair_first(m, ctx);
+ return lsp_mem_create_pair(m, list, m->nil, result);
+}
+
+
+lsp_status_t lsp_ctx_add(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t value) {
+ lsp_status_t status;
+
+ if (contains_symbol(m, ctx, symbol)) {
+ status = remove_symbol(m, ctx, symbol);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ lsp_addr_t list = lsp_mem_get_pair_first(m, ctx);
+
+ lsp_addr_t entry;
+ status = lsp_mem_create_pair(m, symbol, value, &entry);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_mem_create_pair(m, entry, list, &list);
+ lsp_mem_dec_ref(m, entry);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_mem_set_pair_first(m, ctx, list);
+ lsp_mem_dec_ref(m, list);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_ctx_set(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t value) {
+ lsp_addr_t list = lsp_mem_get_pair_first(m, ctx);
+
+ while (list != m->nil) {
+ lsp_addr_t entry = lsp_mem_get_pair_first(m, list);
+ lsp_addr_t entry_symbol = lsp_mem_get_pair_first(m, entry);
+
+ if (lsp_mem_eq(m, symbol, entry_symbol)) {
+ lsp_mem_set_pair_second(m, entry, value);
+ return LSP_SUCCESS;
+ }
+
+ list = lsp_mem_get_pair_second(m, list);
+ }
+
+ return LSP_ERR_CTX;
+}
+
+
+lsp_status_t lsp_ctx_get(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t *value) {
+ lsp_addr_t list = lsp_mem_get_pair_first(m, ctx);
+
+ while (list != m->nil) {
+ lsp_addr_t entry = lsp_mem_get_pair_first(m, list);
+ lsp_addr_t entry_symbol = lsp_mem_get_pair_first(m, entry);
+
+ if (lsp_mem_eq(m, symbol, entry_symbol)) {
+ *value = lsp_mem_get_pair_second(m, entry);
+ return lsp_mem_inc_ref(m, *value);
+ }
+
+ list = lsp_mem_get_pair_second(m, list);
+ }
+
+ return LSP_ERR_CTX;
+}
diff --git a/src_c/ctx.h b/src_c/ctx.h
new file mode 100644
index 0000000..02740a0
--- /dev/null
+++ b/src_c/ctx.h
@@ -0,0 +1,16 @@
+#ifndef LISP16_CTX_H
+#define LISP16_CTX_H
+
+#include "mem.h"
+
+
+lsp_status_t lsp_ctx_create(lsp_mem_t *m, lsp_addr_t *ctx);
+lsp_status_t lsp_ctx_copy(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t *result);
+lsp_status_t lsp_ctx_add(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t value);
+lsp_status_t lsp_ctx_set(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t value);
+lsp_status_t lsp_ctx_get(lsp_mem_t *m, lsp_addr_t ctx, lsp_addr_t symbol,
+ lsp_addr_t *value);
+
+#endif
diff --git a/src_c/env.c b/src_c/env.c
new file mode 100644
index 0000000..13999d5
--- /dev/null
+++ b/src_c/env.c
@@ -0,0 +1,86 @@
+#include "env.h"
+#include "ctx.h"
+#include "eval.h"
+
+
+static void init_result(lsp_env_t *e) {
+ e->result.is_value = true;
+ e->result.ctx = e->m->nil;
+ e->result.value = e->m->nil;
+}
+
+
+static void release_result(lsp_env_t *e) {
+ lsp_mem_dec_ref(e->m, e->result.ctx);
+ lsp_mem_dec_ref(e->m, e->result.value);
+
+ init_result(e);
+}
+
+
+void lsp_env_init(lsp_env_t *e, lsp_mem_t *m, lsp_in_stream_t *in,
+ lsp_out_stream_t *out) {
+ e->m = m;
+ e->in = in;
+ e->out = out;
+
+ init_result(e);
+}
+
+
+lsp_status_t lsp_env_set_result_value(lsp_env_t *e, lsp_addr_t value) {
+ release_result(e);
+
+ lsp_status_t status = lsp_mem_inc_ref(e->m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ e->result.is_value = true;
+ e->result.value = value;
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_env_set_result_eval(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t value) {
+ release_result(e);
+
+ lsp_status_t status = lsp_mem_inc_ref(e->m, ctx);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_mem_inc_ref(e->m, value);
+ if (status != LSP_SUCCESS) {
+ lsp_mem_dec_ref(e->m, ctx);
+ return status;
+ }
+
+ e->result.is_value = false;
+ e->result.ctx = ctx;
+ e->result.value = value;
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_env_resolve(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t value,
+ lsp_addr_t *result) {
+ lsp_status_t status = lsp_env_set_result_eval(e, ctx, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ while (!e->result.is_value) {
+ lsp_addr_t eval_ctx = e->result.ctx;
+ lsp_addr_t eval_value = e->result.value;
+ init_result(e);
+
+ status = lsp_eval(e, eval_ctx, eval_value);
+ lsp_mem_dec_ref(e->m, eval_ctx);
+ lsp_mem_dec_ref(e->m, eval_value);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ *result = e->result.value;
+ init_result(e);
+ return LSP_SUCCESS;
+}
diff --git a/src_c/env.h b/src_c/env.h
new file mode 100644
index 0000000..026cd18
--- /dev/null
+++ b/src_c/env.h
@@ -0,0 +1,30 @@
+#ifndef LISP16_ENV_H
+#define LISP16_ENV_H
+
+#include "mem.h"
+#include "stream.h"
+
+
+typedef struct {
+ lsp_mem_t *m;
+ lsp_in_stream_t *in;
+ lsp_out_stream_t *out;
+
+ // internal
+ struct {
+ lsp_bool_t is_value;
+ lsp_addr_t ctx;
+ lsp_addr_t value;
+ } result;
+} lsp_env_t;
+
+
+void lsp_env_init(lsp_env_t *e, lsp_mem_t *m, lsp_in_stream_t *in,
+ lsp_out_stream_t *out);
+lsp_status_t lsp_env_set_result_value(lsp_env_t *e, lsp_addr_t value);
+lsp_status_t lsp_env_set_result_eval(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t value);
+lsp_status_t lsp_env_resolve(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t value,
+ lsp_addr_t *result);
+
+#endif
diff --git a/src_c/eval.c b/src_c/eval.c
new file mode 100644
index 0000000..e04537e
--- /dev/null
+++ b/src_c/eval.c
@@ -0,0 +1,92 @@
+#include "eval.h"
+#include "ctx.h"
+#include "apply.h"
+
+
+static lsp_status_t eval_args(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args,
+ lsp_addr_t *result) {
+ lsp_addr_t last = e->m->nil;
+ *result = e->m->nil;
+ lsp_status_t status;
+
+ while (args != e->m->nil) {
+ lsp_addr_t arg = lsp_mem_get_pair_first(e->m, args);
+ status = lsp_env_resolve(e, ctx, arg, &arg);
+ if (status != LSP_SUCCESS)
+ goto error;
+
+ lsp_addr_t new_last;
+ status = lsp_mem_create_pair(e->m, arg, e->m->nil, &new_last);
+ lsp_mem_dec_ref(e->m, arg);
+ if (status != LSP_SUCCESS)
+ goto error;
+
+ if (*result == e->m->nil) {
+ *result = new_last;
+
+ } else {
+ lsp_mem_set_pair_second(e->m, last, new_last);
+ lsp_mem_dec_ref(e->m, new_last);
+ }
+
+ last = new_last;
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ return LSP_SUCCESS;
+
+error:
+ lsp_mem_dec_ref(e->m, *result);
+ return status;
+}
+
+
+lsp_status_t lsp_eval(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t value) {
+ lsp_status_t status = LSP_SUCCESS;
+
+ if (lsp_mem_is_number(e->m, value) || lsp_mem_is_string(e->m, value) ||
+ lsp_mem_is_builtin(e->m, value) ||
+ lsp_mem_is_function_or_syntax(e->m, value))
+ return lsp_env_set_result_value(e, value);
+
+ if (lsp_mem_is_symbol(e->m, value)) {
+ lsp_addr_t result;
+ status = lsp_ctx_get(e->m, ctx, value, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+ }
+
+ if (lsp_mem_is_pair(e->m, value)) {
+ if (value == e->m->nil)
+ return lsp_env_set_result_value(e, e->m->nil);
+
+ lsp_addr_t callable = lsp_mem_get_pair_first(e->m, value);
+ lsp_addr_t args = lsp_mem_get_pair_second(e->m, value);
+
+ status = lsp_env_resolve(e, ctx, callable, &callable);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (lsp_mem_is_builtin_function(e->m, callable) ||
+ lsp_mem_is_function(e->m, callable)) {
+ status = eval_args(e, ctx, args, &args);
+ } else {
+ status = lsp_mem_inc_ref(e->m, args);
+ }
+ if (status != LSP_SUCCESS) {
+ lsp_mem_dec_ref(e->m, callable);
+ return status;
+ }
+
+ status = lsp_apply(e, ctx, callable, args);
+ lsp_mem_dec_ref(e->m, callable);
+ lsp_mem_dec_ref(e->m, args);
+ return status;
+ }
+
+ return LSP_ERR_EVAL;
+}
diff --git a/src_c/eval.h b/src_c/eval.h
new file mode 100644
index 0000000..4f40270
--- /dev/null
+++ b/src_c/eval.h
@@ -0,0 +1,9 @@
+#ifndef LISP16_EVAL_H
+#define LISP16_EVAL_H
+
+#include "env.h"
+
+
+lsp_status_t lsp_eval(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t value);
+
+#endif
diff --git a/src_c/function.c b/src_c/function.c
new file mode 100644
index 0000000..075261a
--- /dev/null
+++ b/src_c/function.c
@@ -0,0 +1,582 @@
+#include "function.h"
+#include "apply.h"
+#include "eval.h"
+#include "read.h"
+#include "write.h"
+
+
+lsp_builtin_entry_t lsp_functions[] = {
+ {"eval", lsp_function_eval},
+ {"apply", lsp_function_apply},
+ {"error", lsp_function_error},
+ {"cons", lsp_function_cons},
+ {"set-car!", lsp_function_set_car},
+ {"set-cdr!", lsp_function_set_cdr},
+ {"number?", lsp_function_is_number},
+ {"pair?", lsp_function_is_pair},
+ {"string?", lsp_function_is_string},
+ {"symbol?", lsp_function_is_symbol},
+ {"function?", lsp_function_is_function},
+ {"syntax?", lsp_function_is_syntax},
+ {"eq?", lsp_function_eq},
+ {"equal?", lsp_function_equal},
+ {">", lsp_function_gt},
+ {"<", lsp_function_lt},
+ {"+", lsp_function_plus},
+ {"-", lsp_function_minus},
+ {"*", lsp_function_multiply},
+ {"/", lsp_function_divide},
+ {"read", lsp_function_read},
+ {"read-u8", lsp_function_read_u8},
+ {"peek-u8", lsp_function_peek_u8},
+ {"write", lsp_function_write},
+ {"write-u8", lsp_function_write_u8},
+ {"make-string", lsp_function_make_string},
+ {"string-length", lsp_function_string_length},
+ {"string-ref", lsp_function_string_ref},
+ {"string-set!", lsp_function_string_set},
+ {NULL, NULL}};
+
+
+lsp_status_t lsp_function_eval(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_eval(e, ctx, value);
+}
+
+
+lsp_status_t lsp_function_apply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t callable;
+ lsp_addr_t arguments;
+ lsp_status_t status =
+ lsp_builtin_get_args_2(e->m, args, &callable, &arguments);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_builtin(e->m, callable) &&
+ !lsp_mem_is_function_or_syntax(e->m, callable))
+ return LSP_ERR_ARG_TYPE;
+
+ return lsp_apply(e, ctx, callable, arguments);
+}
+
+
+lsp_status_t lsp_function_error(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_int32_t code = lsp_mem_get_number(e->m, value);
+ if (code < 0 || code > 126)
+ return LSP_ERR_ARG_VALUE;
+
+ return LSP_ERR_USER + code;
+}
+
+
+lsp_status_t lsp_function_cons(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t first;
+ lsp_addr_t second;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &first, &second);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result;
+ status = lsp_mem_create_pair(e->m, first, second, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_set_car(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t pair;
+ lsp_addr_t first;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &pair, &first);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_pair(e->m, pair))
+ return LSP_ERR_ARG_TYPE;
+
+ if (pair == e->m->nil)
+ return LSP_ERR_ARG_VALUE;
+
+ lsp_mem_set_pair_first(e->m, pair, first);
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_function_set_cdr(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t pair;
+ lsp_addr_t second;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &pair, &second);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_pair(e->m, pair))
+ return LSP_ERR_ARG_TYPE;
+
+ if (pair == e->m->nil)
+ return LSP_ERR_ARG_VALUE;
+
+ lsp_mem_set_pair_second(e->m, pair, second);
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_function_is_number(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_number = lsp_mem_is_number(e->m, value);
+ return lsp_env_set_result_value(e, (is_number ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_is_pair(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_pair = lsp_mem_is_pair(e->m, value);
+ return lsp_env_set_result_value(e, (is_pair ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_is_string(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_string = lsp_mem_is_string(e->m, value);
+ return lsp_env_set_result_value(e, (is_string ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_is_symbol(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_symbol = lsp_mem_is_symbol(e->m, value);
+ return lsp_env_set_result_value(e, (is_symbol ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_is_function(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_function = lsp_mem_is_function(e->m, value) ||
+ lsp_mem_is_builtin_function(e->m, value);
+ return lsp_env_set_result_value(e, (is_function ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_is_syntax(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_syntax = lsp_mem_is_syntax(e->m, value) ||
+ lsp_mem_is_builtin_syntax(e->m, value);
+ return lsp_env_set_result_value(e, (is_syntax ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_eq(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t first;
+ lsp_addr_t second;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &first, &second);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_eq = lsp_mem_eq(e->m, first, second);
+ return lsp_env_set_result_value(e, (is_eq ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_equal(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t first;
+ lsp_addr_t second;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &first, &second);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_equal = lsp_mem_equal(e->m, first, second);
+ return lsp_env_set_result_value(e, (is_equal ? e->m->one : e->m->zero));
+}
+
+
+lsp_status_t lsp_function_gt(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t last_value = e->m->nil;
+
+ while (args != e->m->nil) {
+ lsp_addr_t value = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ if (last_value != e->m->nil && lsp_mem_get_number(e->m, last_value) <=
+ lsp_mem_get_number(e->m, value))
+ return lsp_env_set_result_value(e, e->m->zero);
+
+ last_value = value;
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ return lsp_env_set_result_value(e, e->m->one);
+}
+
+
+lsp_status_t lsp_function_lt(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t last_value = e->m->nil;
+
+ while (args != e->m->nil) {
+ lsp_addr_t value = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ if (last_value != e->m->nil && lsp_mem_get_number(e->m, last_value) >=
+ lsp_mem_get_number(e->m, value))
+ return lsp_env_set_result_value(e, e->m->zero);
+
+ last_value = value;
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ return lsp_env_set_result_value(e, e->m->one);
+}
+
+
+lsp_status_t lsp_function_plus(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_int32_t value = 0;
+
+ while (args != e->m->nil) {
+ lsp_addr_t i = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, i))
+ return LSP_ERR_ARG_TYPE;
+
+ value += lsp_mem_get_number(e->m, i);
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ lsp_addr_t result;
+ lsp_status_t status = lsp_mem_create_number(e->m, value, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_minus(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_int32_t value;
+ lsp_uint16_t counter = 0;
+
+ while (args != e->m->nil) {
+ lsp_addr_t i = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, i))
+ return LSP_ERR_ARG_TYPE;
+
+ if (++counter > 1) {
+ value -= lsp_mem_get_number(e->m, i);
+ } else {
+ value = lsp_mem_get_number(e->m, i);
+ }
+
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ if (!counter)
+ return LSP_ERR_ARG_COUNT;
+
+ if (counter < 2)
+ value = -value;
+
+ lsp_addr_t result;
+ lsp_status_t status = lsp_mem_create_number(e->m, value, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_multiply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_int32_t value = 1;
+
+ while (args != e->m->nil) {
+ lsp_addr_t i = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, i))
+ return LSP_ERR_ARG_TYPE;
+
+ value *= lsp_mem_get_number(e->m, i);
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ lsp_addr_t result;
+ lsp_status_t status = lsp_mem_create_number(e->m, value, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_divide(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_int32_t value;
+ lsp_uint16_t counter = 0;
+
+ while (args != e->m->nil) {
+ lsp_addr_t i = lsp_mem_get_pair_first(e->m, args);
+ if (!lsp_mem_is_number(e->m, i))
+ return LSP_ERR_ARG_TYPE;
+
+ if (++counter > 1) {
+ value /= lsp_mem_get_number(e->m, i);
+ } else {
+ value = lsp_mem_get_number(e->m, i);
+ }
+
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ if (!counter)
+ return LSP_ERR_ARG_COUNT;
+
+ if (counter < 2)
+ value = 1 / value;
+
+ lsp_addr_t result;
+ lsp_status_t status = lsp_mem_create_number(e->m, value, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+
+lsp_status_t lsp_function_read(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ if (args != e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t value;
+ lsp_status_t status = lsp_read(e->m, e->in, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, value);
+ lsp_mem_dec_ref(e->m, value);
+ return status;
+}
+
+
+lsp_status_t lsp_function_read_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ if (args != e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(e->in, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result;
+ status = lsp_mem_create_number(e->m, c, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_peek_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ if (args != e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_peek(e->in, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result;
+ status = lsp_mem_create_number(e->m, c, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_write(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_write(e->m, e->out, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_function_write_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_uint8_t c = lsp_mem_get_number(e->m, value);
+ status = lsp_out_stream_write(e->out, c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_function_make_string(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_uint16_t str_len = lsp_mem_get_number(e->m, value);
+
+ lsp_addr_t result;
+ status = lsp_mem_create_string(e->m, str_len, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_string_length(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t str;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &str);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_string(e->m, str))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_uint16_t str_len = lsp_mem_get_string_len(e->m, str);
+
+ lsp_addr_t result;
+ status = lsp_mem_create_number(e->m, str_len, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_string_ref(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t str;
+ lsp_addr_t index;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &str, &index);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_string(e->m, str) || !lsp_mem_is_number(e->m, index))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_uint16_t i = lsp_mem_get_number(e->m, index);
+ lsp_uint16_t str_len = lsp_mem_get_string_len(e->m, str);
+ if (i >= str_len)
+ return LSP_ERR_ARG_VALUE;
+
+ lsp_uint8_t c = lsp_mem_get_string_data(e->m, str, i);
+
+ lsp_addr_t result;
+ status = lsp_mem_create_number(e->m, c, &result);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_function_string_set(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args) {
+ lsp_addr_t str;
+ lsp_addr_t index;
+ lsp_addr_t value;
+ lsp_status_t status =
+ lsp_builtin_get_args_3(e->m, args, &str, &index, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_string(e->m, str) || !lsp_mem_is_number(e->m, index) ||
+ !lsp_mem_is_number(e->m, value))
+ return LSP_ERR_ARG_TYPE;
+
+ lsp_uint16_t i = lsp_mem_get_number(e->m, index);
+ lsp_uint16_t str_len = lsp_mem_get_string_len(e->m, str);
+ if (i >= str_len)
+ return LSP_ERR_ARG_VALUE;
+
+ lsp_mem_set_string_data(e->m, str, i, lsp_mem_get_number(e->m, value));
+
+ return lsp_env_set_result_value(e, e->m->nil);
+}
diff --git a/src_c/function.h b/src_c/function.h
new file mode 100644
index 0000000..ce249c4
--- /dev/null
+++ b/src_c/function.h
@@ -0,0 +1,54 @@
+#ifndef LISP16_FUNCTION_H
+#define LISP16_FUNCTION_H
+
+#include "builtin.h"
+
+
+extern lsp_builtin_entry_t lsp_functions[];
+
+lsp_status_t lsp_function_eval(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_apply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_error(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_cons(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_set_car(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_set_cdr(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_number(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_pair(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_string(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_symbol(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_function(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_is_syntax(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_eq(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_equal(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_gt(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_lt(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_plus(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_minus(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_multiply(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_divide(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_read(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_read_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_peek_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_write(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_function_write_u8(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_make_string(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_string_length(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_string_ref(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+lsp_status_t lsp_function_string_set(lsp_env_t *e, lsp_addr_t ctx,
+ lsp_addr_t args);
+
+#endif
diff --git a/src_c/main.c b/src_c/main.c
new file mode 100644
index 0000000..c09a38b
--- /dev/null
+++ b/src_c/main.c
@@ -0,0 +1,55 @@
+#include "ctx.h"
+#include "repl.h"
+
+#if LSP_ARCH == LSP_ARCH_POSIX
+#include "arch/posix.h"
+#elif LSP_ARCH == LSP_ARCH_AVR8
+#include "arch/avr8.h"
+#elif LSP_ARCH == LSP_ARCH_STM32
+#include "arch/stm32.h"
+#endif
+
+
+int main() {
+ lsp_mem_t *m = NULL;
+ lsp_in_stream_t *in = NULL;
+ lsp_out_stream_t *out = NULL;
+ lsp_status_t status = LSP_ERR;
+
+ LSP_ARCH_INIT();
+
+ m = LSP_ARCH_CREATE_MEM();
+ if (!m)
+ goto cleanup;
+
+ in = LSP_ARCH_CREATE_IN_STREAM();
+ if (!in)
+ goto cleanup;
+
+ out = LSP_ARCH_CREATE_OUT_STREAM();
+ if (!out)
+ goto cleanup;
+
+ lsp_env_t e;
+ lsp_env_init(&e, m, in, out);
+
+ lsp_addr_t ctx;
+ status = lsp_ctx_create(m, &ctx);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ status = lsp_repl(&e, ctx);
+ lsp_mem_dec_ref(m, ctx);
+
+cleanup:
+ if (out)
+ LSP_ARCH_FREE_OUT_STREAM(out);
+
+ if (in)
+ LSP_ARCH_FREE_IN_STREAM(in);
+
+ if (m)
+ LSP_ARCH_FREE_MEM(m);
+
+ return ((status == LSP_EOF) ? 0 : 1);
+}
diff --git a/src_c/mem.c b/src_c/mem.c
new file mode 100644
index 0000000..31ca83d
--- /dev/null
+++ b/src_c/mem.c
@@ -0,0 +1,478 @@
+#include "mem.h"
+
+
+static inline lsp_bool_t get_mark(lsp_cell_t *c) {
+ return ((*c & 0x8000) ? true : false);
+}
+
+
+static inline void set_mark(lsp_cell_t *c, lsp_bool_t mark) {
+ if (mark) {
+ *c |= 0x8000;
+ } else {
+ *c &= 0x7fff;
+ }
+}
+
+
+static void restore(lsp_mem_t *m, lsp_addr_t addr) {
+ while (true) {
+ lsp_cell_t *c = m->cells + addr;
+ if (!get_mark(c))
+ break;
+
+ lsp_uint16_t c_size = lsp_cell_get_size(c);
+ for (lsp_uint16_t i = 0; i < c_size; ++i)
+ set_mark(c + i, false);
+
+ if (lsp_cell_is_pair(c)) {
+ restore(m, lsp_cell_get_pair_first(c));
+ addr = lsp_cell_get_pair_second(c);
+
+ } else if (lsp_cell_is_function(c)) {
+ restore(m, lsp_cell_get_function_parent_ctx(c));
+ restore(m, lsp_cell_get_function_args(c));
+ addr = lsp_cell_get_function_body(c);
+
+ } else if (lsp_cell_is_syntax(c)) {
+ restore(m, lsp_cell_get_syntax_parent_ctx(c));
+ restore(m, lsp_cell_get_syntax_args(c));
+ addr = lsp_cell_get_syntax_body(c);
+ }
+ }
+}
+
+
+static void mark_and_restore(lsp_mem_t *m) {
+ for (lsp_addr_t addr = 0; addr < m->size; ++addr)
+ set_mark(m->cells + addr, true);
+
+ restore(m, m->nil);
+ restore(m, m->zero);
+ restore(m, m->one);
+ restore(m, m->quote);
+ restore(m, m->quasiquote);
+ restore(m, m->unquote);
+ restore(m, m->unquote_splicing);
+ restore(m, m->root);
+}
+
+
+static lsp_bool_t is_free_cell(lsp_mem_t *m, lsp_addr_t addr, lsp_uint16_t size,
+ lsp_addr_t *used_addr) {
+ for (lsp_addr_t i = addr; i < addr + size; ++i) {
+ if (!get_mark(m->cells + i)) {
+ *used_addr = i;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static lsp_status_t find_free_cell(lsp_mem_t *m, lsp_uint16_t size,
+ lsp_addr_t *addr) {
+ if (!size)
+ return LSP_ERR_MEM;
+
+ for (lsp_addr_t i = m->last_addr; i < m->size - size; ++i) {
+ if (is_free_cell(m, i, size, &i)) {
+ *addr = i;
+ m->last_addr = i;
+ return LSP_SUCCESS;
+ }
+ }
+
+ for (lsp_addr_t i = 0; i < m->last_addr && i < m->size - size; ++i) {
+ if (is_free_cell(m, i, size, &i)) {
+ *addr = i;
+ m->last_addr = i;
+ return LSP_SUCCESS;
+ }
+ }
+
+ return LSP_ERR_MEM;
+}
+
+
+static lsp_status_t find_free_cell_with_gc(lsp_mem_t *m, lsp_uint16_t size,
+ lsp_addr_t *addr) {
+ lsp_status_t status = find_free_cell(m, size, addr);
+ if (status == LSP_SUCCESS)
+ return LSP_SUCCESS;
+
+ mark_and_restore(m);
+
+ return find_free_cell(m, size, addr);
+}
+
+
+static lsp_status_t alloc_cell(lsp_mem_t *m, lsp_uint16_t size,
+ lsp_addr_t *addr) {
+ lsp_addr_t root;
+ lsp_status_t status = find_free_cell_with_gc(m, 2, &root);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_pair(m->cells + root, m->nil, m->root);
+ m->root = root;
+
+ status = find_free_cell_with_gc(m, size, addr);
+ if (status != LSP_SUCCESS) {
+ m->root = lsp_cell_get_pair_second(m->cells + root);
+ return status;
+ }
+
+ lsp_mem_set_pair_first(m, root, *addr);
+ return LSP_SUCCESS;
+}
+
+
+static lsp_bool_t is_symbol_from_string(lsp_mem_t *m, lsp_addr_t symbol,
+ lsp_addr_t str) {
+ lsp_uint16_t str_len = lsp_cell_get_string_len(m->cells + str);
+ if (lsp_mem_get_symbol_len(m, symbol) != str_len)
+ return false;
+
+ for (lsp_uint16_t i = 0; i < str_len; ++i)
+ if (lsp_mem_get_symbol_name(m, symbol, i) !=
+ lsp_mem_get_string_data(m, str, i))
+ return false;
+
+ return true;
+}
+
+
+static lsp_bool_t is_symbol_from_char(lsp_mem_t *m, lsp_addr_t symbol,
+ char *name, lsp_uint16_t name_len) {
+ if (lsp_mem_get_symbol_len(m, symbol) != name_len)
+ return false;
+
+ for (lsp_uint16_t i = 0; i < name_len; ++i)
+ if (lsp_mem_get_symbol_name(m, symbol, i) != name[i])
+ return false;
+
+ return true;
+}
+
+
+static lsp_status_t find_symbol_from_string(lsp_mem_t *m, lsp_addr_t str,
+ lsp_addr_t *addr) {
+ for (lsp_addr_t i = 0; i < m->size; ++i) {
+ if (get_mark(m->cells + i))
+ continue;
+
+ if (lsp_cell_is_symbol(m->cells + i) &&
+ is_symbol_from_string(m, i, str)) {
+ *addr = i;
+ return LSP_SUCCESS;
+ }
+
+ i += lsp_cell_get_size(m->cells + i) - 1;
+ }
+
+ return LSP_ERR;
+}
+
+
+static lsp_status_t find_symbol_from_char(lsp_mem_t *m, char *name,
+ lsp_uint16_t name_len,
+ lsp_addr_t *addr) {
+ for (lsp_addr_t i = 0; i < m->size; ++i) {
+ if (get_mark(m->cells + i))
+ continue;
+
+ if (lsp_cell_is_symbol(m->cells + i) &&
+ is_symbol_from_char(m, i, name, name_len)) {
+ *addr = i;
+ return LSP_SUCCESS;
+ }
+
+ i += lsp_cell_get_size(m->cells + i) - 1;
+ }
+
+ return LSP_ERR;
+}
+
+
+lsp_status_t lsp_mem_init(lsp_mem_t *m, lsp_uint16_t size) {
+ m->size = size;
+ m->last_addr = 0;
+
+ for (lsp_addr_t addr = 0; addr < size; ++addr)
+ set_mark(m->cells + addr, true);
+
+ lsp_status_t status = find_free_cell(m, 2, &(m->nil));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_pair(m->cells + m->nil, m->nil, m->nil);
+
+ status = find_free_cell(m, lsp_cell_get_number_size(0), &(m->zero));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_number(m->cells + m->zero, 0);
+
+ status = find_free_cell(m, lsp_cell_get_number_size(1), &(m->one));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_number(m->cells + m->one, 1);
+
+ status = find_free_cell(m, lsp_cell_get_string_symbol_size(5), &(m->quote));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_symbol(m->cells + m->quote, 5);
+ for (lsp_uint16_t i = 0; i < 5; ++i)
+ lsp_cell_set_symbol_name(m->cells + m->quote, i, ("quote")[i]);
+
+ status = find_free_cell(m, lsp_cell_get_string_symbol_size(10),
+ &(m->quasiquote));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_symbol(m->cells + m->quasiquote, 10);
+ for (lsp_uint16_t i = 0; i < 10; ++i)
+ lsp_cell_set_symbol_name(m->cells + m->quasiquote, i,
+ ("quasiquote")[i]);
+
+ status =
+ find_free_cell(m, lsp_cell_get_string_symbol_size(7), &(m->unquote));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_symbol(m->cells + m->unquote, 7);
+ for (lsp_uint16_t i = 0; i < 7; ++i)
+ lsp_cell_set_symbol_name(m->cells + m->unquote, i, ("unquote")[i]);
+
+ status = find_free_cell(m, lsp_cell_get_string_symbol_size(16),
+ &(m->unquote_splicing));
+ if (status != LSP_SUCCESS)
+ return status;
+ lsp_cell_set_symbol(m->cells + m->unquote_splicing, 16);
+ for (lsp_uint16_t i = 0; i < 16; ++i)
+ lsp_cell_set_symbol_name(m->cells + m->unquote_splicing, i,
+ ("unquote-splicing")[i]);
+
+ m->root = m->nil;
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_inc_ref(lsp_mem_t *m, lsp_addr_t addr) {
+ if (addr == m->nil || addr == m->zero || addr == m->one || addr == m->quote)
+ return LSP_SUCCESS;
+
+ lsp_addr_t root;
+ lsp_status_t status = find_free_cell_with_gc(m, 2, &root);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_pair(m->cells + root, addr, m->root);
+ m->root = root;
+ return LSP_SUCCESS;
+}
+
+
+void lsp_mem_dec_ref(lsp_mem_t *m, lsp_addr_t addr) {
+ if (addr == m->nil || addr == m->zero || addr == m->one || addr == m->quote)
+ return;
+
+ lsp_addr_t curr_addr = m->root;
+ lsp_addr_t prev_addr = m->nil;
+ while (curr_addr != m->nil) {
+ lsp_addr_t first = lsp_cell_get_pair_first(m->cells + curr_addr);
+ lsp_addr_t second = lsp_cell_get_pair_second(m->cells + curr_addr);
+
+ if (first == addr) {
+ if (prev_addr == m->nil) {
+ m->root = second;
+
+ } else {
+ lsp_mem_set_pair_second(m, prev_addr, second);
+ }
+
+ return;
+ }
+
+ prev_addr = curr_addr;
+ curr_addr = second;
+ }
+}
+
+
+lsp_status_t lsp_mem_create_number(lsp_mem_t *m, lsp_int32_t value,
+ lsp_addr_t *addr) {
+ lsp_uint16_t size = lsp_cell_get_number_size(value);
+ lsp_status_t status = alloc_cell(m, size, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_number(m->cells + *addr, value);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_pair(lsp_mem_t *m, lsp_addr_t first,
+ lsp_addr_t second, lsp_addr_t *addr) {
+ lsp_status_t status = alloc_cell(m, 2, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_pair(m->cells + *addr, first, second);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_string(lsp_mem_t *m, lsp_uint16_t data_len,
+ lsp_addr_t *addr) {
+ lsp_uint16_t size = lsp_cell_get_string_symbol_size(data_len);
+ lsp_status_t status = alloc_cell(m, size, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_string(m->cells + *addr, data_len);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_symbol_from_string(lsp_mem_t *m, lsp_addr_t str,
+ lsp_addr_t *addr) {
+ if (find_symbol_from_string(m, str, addr) == LSP_SUCCESS)
+ return lsp_mem_inc_ref(m, *addr);
+
+ lsp_uint16_t name_len = lsp_mem_get_string_len(m, str);
+ lsp_uint16_t size = lsp_cell_get_string_symbol_size(name_len);
+ lsp_status_t status = alloc_cell(m, size, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_symbol(m->cells + *addr, name_len);
+ for (lsp_uint16_t i = 0; i < name_len; ++i)
+ lsp_cell_set_symbol_name(m->cells + *addr, i,
+ lsp_mem_get_string_data(m, str, i));
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_symbol_from_char(lsp_mem_t *m, char *name,
+ lsp_addr_t *addr) {
+ lsp_uint16_t name_len = 0;
+ while (name[name_len])
+ name_len++;
+
+ if (find_symbol_from_char(m, name, name_len, addr) == LSP_SUCCESS)
+ return lsp_mem_inc_ref(m, *addr);
+
+ lsp_uint16_t size = lsp_cell_get_string_symbol_size(name_len);
+ lsp_status_t status = alloc_cell(m, size, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_symbol(m->cells + *addr, name_len);
+ for (lsp_uint16_t i = 0; i < name_len; ++i)
+ lsp_cell_set_symbol_name(m->cells + *addr, i, name[i]);
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_builtin_function(lsp_mem_t *m, lsp_uint16_t index,
+ lsp_addr_t *addr) {
+ lsp_status_t status = alloc_cell(m, 1, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_builtin_function(m->cells + *addr, index);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_builtin_syntax(lsp_mem_t *m, lsp_uint16_t index,
+ lsp_addr_t *addr) {
+ lsp_status_t status = alloc_cell(m, 1, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_builtin_syntax(m->cells + *addr, index);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_function(lsp_mem_t *m, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body,
+ lsp_addr_t *addr) {
+ lsp_status_t status = alloc_cell(m, 4, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_function(m->cells + *addr, parent_ctx, args, body);
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_mem_create_syntax(lsp_mem_t *m, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body,
+ lsp_addr_t *addr) {
+ lsp_status_t status = alloc_cell(m, 4, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_cell_set_syntax(m->cells + *addr, parent_ctx, args, body);
+ return LSP_SUCCESS;
+}
+
+
+lsp_bool_t lsp_mem_eq(lsp_mem_t *m, lsp_addr_t a1, lsp_addr_t a2) {
+ if (a1 == a2)
+ return true;
+
+ if (lsp_mem_is_number(m, a1)) {
+ if (!lsp_mem_is_number(m, a2))
+ return false;
+
+ return lsp_mem_get_number(m, a1) == lsp_mem_get_number(m, a2);
+ }
+
+ return false;
+}
+
+
+lsp_bool_t lsp_mem_equal(lsp_mem_t *m, lsp_addr_t a1, lsp_addr_t a2) {
+ if (lsp_mem_eq(m, a1, a2))
+ return true;
+
+ if (lsp_mem_is_pair(m, a1)) {
+ if (!lsp_mem_is_pair(m, a2))
+ return false;
+
+ while (a1 != m->nil && a2 != m->nil) {
+ if (!lsp_mem_equal(m, lsp_mem_get_pair_first(m, a1),
+ lsp_mem_get_pair_first(m, a2)))
+ return false;
+
+ a1 = lsp_mem_get_pair_second(m, a1);
+ a2 = lsp_mem_get_pair_second(m, a2);
+ }
+
+ return a1 == a2;
+ }
+
+ if (lsp_mem_is_string(m, a1)) {
+ if (!lsp_mem_is_string(m, a2))
+ return false;
+
+ lsp_uint16_t a1_len = lsp_mem_get_string_len(m, a1);
+ lsp_uint16_t a2_len = lsp_mem_get_string_len(m, a2);
+ if (a1_len != a2_len)
+ return false;
+
+ for (lsp_uint16_t i = 0; i < a1_len; ++i) {
+ if (lsp_mem_get_string_data(m, a1, i) !=
+ lsp_mem_get_string_data(m, a2, i))
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src_c/mem.h b/src_c/mem.h
new file mode 100644
index 0000000..7d72ba9
--- /dev/null
+++ b/src_c/mem.h
@@ -0,0 +1,189 @@
+#ifndef LISP16_MEM_H
+#define LISP16_MEM_H
+
+#include "cell.h"
+#include "status.h"
+
+
+typedef struct {
+ lsp_addr_t nil;
+ lsp_addr_t zero;
+ lsp_addr_t one;
+ lsp_addr_t quote;
+ lsp_addr_t quasiquote;
+ lsp_addr_t unquote;
+ lsp_addr_t unquote_splicing;
+
+ // internal
+ lsp_uint16_t size;
+ lsp_addr_t last_addr;
+ lsp_addr_t root;
+ lsp_cell_t cells[];
+} lsp_mem_t;
+
+
+lsp_status_t lsp_mem_init(lsp_mem_t *m, lsp_uint16_t size);
+lsp_status_t lsp_mem_inc_ref(lsp_mem_t *m, lsp_addr_t addr);
+void lsp_mem_dec_ref(lsp_mem_t *m, lsp_addr_t addr);
+
+lsp_status_t lsp_mem_create_number(lsp_mem_t *m, lsp_int32_t value,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_pair(lsp_mem_t *m, lsp_addr_t first,
+ lsp_addr_t second, lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_string(lsp_mem_t *m, lsp_uint16_t data_len,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_symbol_from_string(lsp_mem_t *m, lsp_addr_t str,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_symbol_from_char(lsp_mem_t *m, char *name,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_builtin_function(lsp_mem_t *m, lsp_uint16_t index,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_builtin_syntax(lsp_mem_t *m, lsp_uint16_t index,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_function(lsp_mem_t *m, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body,
+ lsp_addr_t *addr);
+lsp_status_t lsp_mem_create_syntax(lsp_mem_t *m, lsp_addr_t parent_ctx,
+ lsp_addr_t args, lsp_addr_t body,
+ lsp_addr_t *addr);
+
+lsp_bool_t lsp_mem_eq(lsp_mem_t *m, lsp_addr_t a1, lsp_addr_t a2);
+lsp_bool_t lsp_mem_equal(lsp_mem_t *m, lsp_addr_t a1, lsp_addr_t a2);
+
+
+static inline lsp_bool_t lsp_mem_is_number(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_number(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_pair(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_pair(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_string(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_string(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_symbol(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_symbol(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_builtin_function(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_is_builtin_function(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_builtin_syntax(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_is_builtin_syntax(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_function(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_function(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_syntax(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_syntax(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_string_or_symbol(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_is_string_or_symbol(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_builtin(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_is_builtin(m->cells + addr);
+}
+
+static inline lsp_bool_t lsp_mem_is_function_or_syntax(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_is_function_or_syntax(m->cells + addr);
+}
+
+
+static inline lsp_int32_t lsp_mem_get_number(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_get_number(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_pair_first(lsp_mem_t *m, lsp_addr_t addr) {
+ return lsp_cell_get_pair_first(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_pair_second(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_pair_second(m->cells + addr);
+}
+
+static inline lsp_uint16_t lsp_mem_get_string_len(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_string_len(m->cells + addr);
+}
+
+static inline lsp_uint8_t lsp_mem_get_string_data(lsp_mem_t *m, lsp_addr_t addr,
+ lsp_uint16_t i) {
+ return lsp_cell_get_string_data(m->cells + addr, i);
+}
+
+static inline lsp_uint16_t lsp_mem_get_symbol_len(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_symbol_len(m->cells + addr);
+}
+
+static inline lsp_uint8_t lsp_mem_get_symbol_name(lsp_mem_t *m, lsp_addr_t addr,
+ lsp_uint16_t i) {
+ return lsp_cell_get_symbol_name(m->cells + addr, i);
+}
+
+static inline lsp_uint16_t lsp_mem_get_builtin_index(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_builtin_index(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_function_parent_ctx(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_function_parent_ctx(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_function_args(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_function_args(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_function_body(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_function_body(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_syntax_parent_ctx(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_syntax_parent_ctx(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_syntax_args(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_syntax_args(m->cells + addr);
+}
+
+static inline lsp_addr_t lsp_mem_get_syntax_body(lsp_mem_t *m,
+ lsp_addr_t addr) {
+ return lsp_cell_get_syntax_body(m->cells + addr);
+}
+
+
+static inline void lsp_mem_set_pair_first(lsp_mem_t *m, lsp_addr_t addr,
+ lsp_addr_t first) {
+ lsp_cell_set_pair(m->cells + addr, first,
+ lsp_cell_get_pair_second(m->cells + addr));
+}
+
+static inline void lsp_mem_set_pair_second(lsp_mem_t *m, lsp_addr_t addr,
+ lsp_addr_t second) {
+ lsp_cell_set_pair(m->cells + addr, lsp_cell_get_pair_first(m->cells + addr),
+ second);
+}
+
+static inline void lsp_mem_set_string_data(lsp_mem_t *m, lsp_addr_t addr,
+ lsp_uint16_t i, lsp_uint8_t data_i) {
+ lsp_cell_set_string_data(m->cells + addr, i, data_i);
+}
+
+#endif
diff --git a/src_c/read.c b/src_c/read.c
new file mode 100644
index 0000000..82250f5
--- /dev/null
+++ b/src_c/read.c
@@ -0,0 +1,428 @@
+#include "read.h"
+#include "buff.h"
+
+
+static inline lsp_bool_t is_ws(lsp_uint8_t v) {
+ return (v == ' ') || (v == '\n') || (v == '\r') || (v == '\t');
+}
+
+
+static inline lsp_bool_t is_comment_start(lsp_uint8_t v) { return v == ';'; }
+
+
+static inline lsp_bool_t is_comment_stop(lsp_uint8_t v) { return v == '\n'; }
+
+
+static inline lsp_bool_t is_digit(lsp_uint8_t v) {
+ return (v >= '0') && (v <= '9');
+}
+
+
+static inline lsp_bool_t is_list_start(lsp_uint8_t v) {
+ return (v == '(') || (v == '[') || (v == '{');
+}
+
+
+static inline lsp_bool_t is_list_stop(lsp_uint8_t v) {
+ return (v == ')') || (v == ']') || (v == '}');
+}
+
+
+static inline lsp_bool_t is_str_start_stop(lsp_uint8_t v) { return v == '"'; }
+
+
+static inline lsp_bool_t is_quote(lsp_uint8_t v) { return v == '\''; }
+
+
+static inline lsp_bool_t is_quasiquote(lsp_uint8_t v) { return v == '`'; }
+
+
+static inline lsp_bool_t is_unquote(lsp_uint8_t v) { return v == ','; }
+
+
+static lsp_status_t skip_ws(lsp_in_stream_t *s) {
+ while (true) {
+ lsp_uint8_t v;
+ lsp_status_t status = lsp_in_stream_peek(s, &v);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (is_comment_start(v)) {
+ while (!is_comment_stop(v)) {
+ status = lsp_in_stream_read(s, &v);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ continue;
+ }
+
+ if (!is_ws(v))
+ return LSP_SUCCESS;
+
+ lsp_in_stream_read(s, &v);
+ }
+}
+
+
+static lsp_status_t read_number(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_int32_t v = 0;
+
+ while (true) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_peek(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (is_ws(c) || is_list_start(c) || is_list_stop(c))
+ break;
+
+ if (!is_digit(c))
+ return LSP_ERR_READ;
+
+ status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ v = (v * 10) + (c - '0');
+ }
+
+ if (v == 0) {
+ *addr = m->zero;
+ return LSP_SUCCESS;
+ }
+
+ if (v == 1) {
+ *addr = m->one;
+ return LSP_SUCCESS;
+ }
+
+ return lsp_mem_create_number(m, v, addr);
+}
+
+
+static lsp_status_t read_list(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_uint8_t list_end;
+ if (c == '(') {
+ list_end = ')';
+ } else if (c == '[') {
+ list_end = ']';
+ } else if (c == '{') {
+ list_end = '}';
+ } else {
+ return LSP_ERR_READ;
+ }
+
+ lsp_bool_t read_rest = false;
+ lsp_bool_t read_stop = false;
+ lsp_addr_t rest = m->nil;
+ lsp_addr_t last = m->nil;
+ *addr = m->nil;
+
+ while (true) {
+ status = skip_ws(s);
+ if (status != LSP_SUCCESS)
+ break;
+
+ status = lsp_in_stream_peek(s, &c);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (is_list_stop(c)) {
+ status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (c != list_end) {
+ status = LSP_ERR_READ;
+ break;
+ }
+
+ lsp_mem_set_pair_second(m, last, rest);
+ lsp_mem_dec_ref(m, rest);
+ return LSP_SUCCESS;
+ }
+
+ if (read_stop) {
+ status = LSP_ERR_READ;
+ break;
+ }
+
+ lsp_addr_t el;
+ status = lsp_read(m, s, &el);
+ if (status != LSP_SUCCESS)
+ break;
+
+ if (lsp_mem_is_symbol(m, el) && lsp_mem_get_symbol_len(m, el) == 1 &&
+ lsp_mem_get_symbol_name(m, el, 0) == '.') {
+ lsp_mem_dec_ref(m, el);
+
+ if (read_rest) {
+ status = LSP_ERR_READ;
+ break;
+ }
+
+ read_rest = true;
+ continue;
+ }
+
+ if (read_rest) {
+ rest = el;
+ read_stop = true;
+ continue;
+ }
+
+ lsp_addr_t new_last;
+ status = lsp_mem_create_pair(m, el, m->nil, &new_last);
+ lsp_mem_dec_ref(m, el);
+ if (status != LSP_SUCCESS)
+ break;
+
+ lsp_mem_set_pair_second(m, last, new_last);
+ last = new_last;
+
+ if (*addr == m->nil) {
+ *addr = last;
+ } else {
+ lsp_mem_dec_ref(m, last);
+ }
+ }
+
+ lsp_mem_dec_ref(m, *addr);
+ lsp_mem_dec_ref(m, rest);
+ return status;
+}
+
+
+static lsp_status_t read_string(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!is_str_start_stop(c))
+ return LSP_ERR_READ;
+
+ lsp_buff_t buff;
+ lsp_buff_init(&buff, m);
+
+ lsp_bool_t read_escaped = false;
+
+ while (true) {
+ status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ if (read_escaped) {
+ if (c == 'n') {
+ status = lsp_buff_push(&buff, '\n');
+ } else if (c == 'r') {
+ status = lsp_buff_push(&buff, '\r');
+ } else if (c == 't') {
+ status = lsp_buff_push(&buff, '\t');
+ } else if (c == '\\') {
+ status = lsp_buff_push(&buff, '\\');
+ } else if (c == '"') {
+ status = lsp_buff_push(&buff, '"');
+ } else {
+ status = LSP_ERR_READ;
+ }
+
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ read_escaped = false;
+ continue;
+ }
+
+ if (c == '\\') {
+ read_escaped = true;
+ continue;
+ }
+
+ if (is_str_start_stop(c))
+ break;
+
+ status = lsp_buff_push(&buff, c);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+ }
+
+ status = lsp_buff_pop(&buff, addr);
+
+cleanup:
+ lsp_buff_clear(&buff);
+ return status;
+}
+
+
+static lsp_status_t read_quote(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!is_quote(c))
+ return LSP_ERR_READ;
+
+ lsp_addr_t value;
+ status = lsp_read(m, s, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t list;
+ status = lsp_mem_create_pair(m, value, m->nil, &list);
+ lsp_mem_dec_ref(m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_mem_create_pair(m, m->quote, list, addr);
+ lsp_mem_dec_ref(m, list);
+ return status;
+}
+
+
+static lsp_status_t read_quasiquote(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!is_quasiquote(c))
+ return LSP_ERR_READ;
+
+ lsp_addr_t value;
+ status = lsp_read(m, s, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t list;
+ status = lsp_mem_create_pair(m, value, m->nil, &list);
+ lsp_mem_dec_ref(m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_mem_create_pair(m, m->quasiquote, list, addr);
+ lsp_mem_dec_ref(m, list);
+ return status;
+}
+
+
+static lsp_status_t read_unquote(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_uint8_t c;
+ lsp_status_t status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!is_unquote(c))
+ return LSP_ERR_READ;
+
+ status = lsp_in_stream_peek(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_splicing = (c == '@');
+ if (is_splicing) {
+ status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ lsp_addr_t value;
+ status = lsp_read(m, s, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t list;
+ status = lsp_mem_create_pair(m, value, m->nil, &list);
+ lsp_mem_dec_ref(m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_mem_create_pair(
+ m, (is_splicing ? m->unquote_splicing : m->unquote), list, addr);
+ lsp_mem_dec_ref(m, list);
+ return status;
+}
+
+
+static lsp_status_t read_symbol(lsp_mem_t *m, lsp_in_stream_t *s,
+ lsp_addr_t *addr) {
+ lsp_status_t status;
+
+ lsp_buff_t buff;
+ lsp_buff_init(&buff, m);
+
+ while (true) {
+ lsp_uint8_t c;
+ status = lsp_in_stream_peek(s, &c);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ if (is_ws(c) || is_list_start(c) || is_list_stop(c))
+ break;
+
+ status = lsp_in_stream_read(s, &c);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ status = lsp_buff_push(&buff, c);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+ }
+
+ lsp_addr_t str;
+ status = lsp_buff_pop(&buff, &str);
+ if (status != LSP_SUCCESS)
+ goto cleanup;
+
+ status = lsp_mem_create_symbol_from_string(m, str, addr);
+ lsp_mem_dec_ref(m, str);
+
+cleanup:
+ lsp_buff_clear(&buff);
+ return status;
+}
+
+
+lsp_status_t lsp_read(lsp_mem_t *m, lsp_in_stream_t *s, lsp_addr_t *addr) {
+ lsp_status_t status = skip_ws(s);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_uint8_t c;
+ status = lsp_in_stream_peek(s, &c);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (is_list_start(c))
+ return read_list(m, s, addr);
+
+ if (is_str_start_stop(c))
+ return read_string(m, s, addr);
+
+ if (is_digit(c))
+ return read_number(m, s, addr);
+
+ if (is_quote(c))
+ return read_quote(m, s, addr);
+
+ if (is_quasiquote(c))
+ return read_quasiquote(m, s, addr);
+
+ if (is_unquote(c))
+ return read_unquote(m, s, addr);
+
+ return read_symbol(m, s, addr);
+}
diff --git a/src_c/read.h b/src_c/read.h
new file mode 100644
index 0000000..15bfb7d
--- /dev/null
+++ b/src_c/read.h
@@ -0,0 +1,10 @@
+#ifndef LISP16_READ_H
+#define LISP16_READ_H
+
+#include "mem.h"
+#include "stream.h"
+
+
+lsp_status_t lsp_read(lsp_mem_t *m, lsp_in_stream_t *s, lsp_addr_t *addr);
+
+#endif
diff --git a/src_c/repl.c b/src_c/repl.c
new file mode 100644
index 0000000..6563613
--- /dev/null
+++ b/src_c/repl.c
@@ -0,0 +1,104 @@
+#include "repl.h"
+#include "ctx.h"
+#include "write.h"
+#include "read.h"
+#include "eval.h"
+
+
+static void log_status(lsp_out_stream_t *s, lsp_status_t status) {
+ if (status == LSP_SUCCESS)
+ return;
+
+ lsp_out_stream_write_str(s, "error: ");
+
+ if (status == LSP_ERR_MEM) {
+ lsp_out_stream_write_str(s, "no memory");
+
+ } else if (status == LSP_ERR_CTX) {
+ lsp_out_stream_write_str(s, "can't resolve symbol");
+
+ } else if (status == LSP_ERR_READ) {
+ lsp_out_stream_write_str(s, "reader error");
+
+ } else if (status == LSP_ERR_WRITE) {
+ lsp_out_stream_write_str(s, "writer error");
+
+ } else if (status == LSP_ERR_EVAL) {
+ lsp_out_stream_write_str(s, "evaluation error");
+
+ } else if (status == LSP_ERR_APPLY) {
+ lsp_out_stream_write_str(s, "application error");
+
+ } else if (status == LSP_ERR_ARG_COUNT) {
+ lsp_out_stream_write_str(s, "invalid argument count");
+
+ } else if (status == LSP_ERR_ARG_TYPE) {
+ lsp_out_stream_write_str(s, "invalid argument type");
+
+ } else if (status == LSP_ERR_ARG_VALUE) {
+ lsp_out_stream_write_str(s, "invalid argument value");
+
+ } else if (status >= LSP_ERR_USER) {
+ lsp_out_stream_write_str(s, "user error ");
+ lsp_out_stream_write_int(s, status - LSP_ERR_USER);
+
+ } else {
+ lsp_out_stream_write_str(s, "other error");
+ }
+
+ lsp_out_stream_write(s, '\n');
+}
+
+
+static lsp_status_t skip_line(lsp_in_stream_t *s) {
+ lsp_uint8_t c;
+ lsp_status_t status;
+
+ do {
+ status = lsp_in_stream_read(s, &c);
+ } while (status == LSP_SUCCESS && c != '\n');
+
+ return status;
+}
+
+
+lsp_status_t lsp_repl(lsp_env_t *e, lsp_addr_t ctx) {
+ while (true) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_read(e->m, e->in, &value);
+ if (status == LSP_EOF)
+ return status;
+ if (status != LSP_SUCCESS) {
+ log_status(e->out, status);
+
+ status = skip_line(e->in);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ continue;
+ }
+
+ lsp_addr_t result;
+ status = lsp_env_resolve(e, ctx, value, &result);
+ lsp_mem_dec_ref(e->m, value);
+ if (status == LSP_EOF)
+ return status;
+ if (status != LSP_SUCCESS) {
+ log_status(e->out, status);
+ continue;
+ }
+
+ if (result == e->m->nil)
+ continue;
+
+ status = lsp_write(e->m, e->out, result);
+ lsp_mem_dec_ref(e->m, result);
+ if (status == LSP_EOF)
+ return status;
+ if (status != LSP_SUCCESS) {
+ log_status(e->out, status);
+ continue;
+ }
+ lsp_out_stream_write(e->out, '\n');
+ }
+}
diff --git a/src_c/repl.h b/src_c/repl.h
new file mode 100644
index 0000000..9c34070
--- /dev/null
+++ b/src_c/repl.h
@@ -0,0 +1,9 @@
+#ifndef LISP16_REPL_H
+#define LISP16_REPL_H
+
+#include "env.h"
+
+
+lsp_status_t lsp_repl(lsp_env_t *e, lsp_addr_t ctx);
+
+#endif
diff --git a/src_c/status.h b/src_c/status.h
new file mode 100644
index 0000000..c6332e9
--- /dev/null
+++ b/src_c/status.h
@@ -0,0 +1,25 @@
+#ifndef LSP_STATUS_H
+#define LSP_STATUS_H
+
+#include "arch.h"
+
+#define LSP_STATUS(x) ((lsp_status_t)x)
+
+#define LSP_SUCCESS LSP_STATUS(0)
+#define LSP_EOF LSP_STATUS(-1)
+#define LSP_ERR LSP_STATUS(1)
+#define LSP_ERR_MEM LSP_STATUS(2)
+#define LSP_ERR_CTX LSP_STATUS(3)
+#define LSP_ERR_READ LSP_STATUS(4)
+#define LSP_ERR_WRITE LSP_STATUS(5)
+#define LSP_ERR_EVAL LSP_STATUS(6)
+#define LSP_ERR_APPLY LSP_STATUS(7)
+#define LSP_ERR_ARG_COUNT LSP_STATUS(8)
+#define LSP_ERR_ARG_TYPE LSP_STATUS(9)
+#define LSP_ERR_ARG_VALUE LSP_STATUS(10)
+#define LSP_ERR_USER LSP_STATUS(0x80)
+
+
+typedef lsp_int8_t lsp_status_t;
+
+#endif
diff --git a/src_c/stream.c b/src_c/stream.c
new file mode 100644
index 0000000..37f5d3d
--- /dev/null
+++ b/src_c/stream.c
@@ -0,0 +1,108 @@
+#include "stream.h"
+
+
+static lsp_int16_t str_getchar(lsp_in_stream_t *s) {
+ lsp_str_stream_t *ss = (lsp_str_stream_t *)s;
+
+ if (ss->pos >= lsp_mem_get_string_len(ss->m, ss->str))
+ return LSP_EOF;
+
+ return lsp_mem_get_string_data(ss->m, ss->str, ss->pos++);
+}
+
+
+void lsp_in_stream_init(lsp_in_stream_t *s, lsp_stream_getchar_t getchar) {
+ s->getchar = getchar;
+ s->next_available = false;
+}
+
+
+lsp_status_t lsp_in_stream_read(lsp_in_stream_t *s, lsp_uint8_t *v) {
+ if (s->next_available) {
+ s->next_available = false;
+ *v = s->next_value;
+ return s->next_status;
+ }
+
+ lsp_int16_t c = s->getchar(s);
+ *v = c;
+ return ((c == LSP_EOF) ? LSP_EOF : LSP_SUCCESS);
+}
+
+
+lsp_status_t lsp_in_stream_peek(lsp_in_stream_t *s, lsp_uint8_t *v) {
+ if (s->next_available) {
+ *v = s->next_value;
+ return s->next_status;
+ }
+
+ lsp_int16_t c = s->getchar(s);
+ s->next_available = true;
+ s->next_value = c;
+ s->next_status = ((c == LSP_EOF) ? LSP_EOF : LSP_SUCCESS);
+
+ *v = s->next_value;
+ return s->next_status;
+}
+
+
+void lsp_out_stream_init(lsp_out_stream_t *s, lsp_stream_putchar_t putchar) {
+ s->putchar = putchar;
+}
+
+
+lsp_status_t lsp_out_stream_write(lsp_out_stream_t *s, lsp_uint8_t v) {
+ if (s->putchar(s, v) == LSP_EOF)
+ return LSP_EOF;
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_out_stream_write_str(lsp_out_stream_t *s, char *str) {
+ while (*str) {
+ lsp_status_t status = lsp_out_stream_write(s, *(str++));
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ return LSP_SUCCESS;
+}
+
+
+lsp_status_t lsp_out_stream_write_int(lsp_out_stream_t *s, lsp_int32_t v) {
+ lsp_status_t status;
+ if (v < 0) {
+ v *= -1;
+ status = lsp_out_stream_write(s, '-');
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ lsp_uint8_t size = 0;
+ for (lsp_int32_t i = v; i; i /= 10)
+ size++;
+ if (size < 1)
+ size = 1;
+
+ for (lsp_uint8_t i = 0; i < size; ++i) {
+ lsp_int32_t temp = v;
+ for (lsp_uint8_t j = i; j < size - 1; ++j)
+ temp /= 10;
+
+ lsp_uint8_t digit = temp % 10;
+ status = lsp_out_stream_write(s, '0' + digit);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ return LSP_SUCCESS;
+}
+
+
+void lsp_str_stream_init(lsp_str_stream_t *s, lsp_mem_t *m, lsp_addr_t str) {
+ lsp_in_stream_init((lsp_in_stream_t *)s, str_getchar);
+ s->m = m;
+ s->str = str;
+ s->pos = 0;
+}
diff --git a/src_c/stream.h b/src_c/stream.h
new file mode 100644
index 0000000..268f69b
--- /dev/null
+++ b/src_c/stream.h
@@ -0,0 +1,46 @@
+#ifndef LISP16_STREAM_H
+#define LISP16_STREAM_H
+
+#include "mem.h"
+
+
+typedef struct lsp_in_stream_t lsp_in_stream_t;
+typedef struct lsp_out_stream_t lsp_out_stream_t;
+typedef lsp_int16_t (*lsp_stream_getchar_t)(lsp_in_stream_t *s);
+typedef lsp_int16_t (*lsp_stream_putchar_t)(lsp_out_stream_t *s, lsp_int16_t v);
+
+struct lsp_in_stream_t {
+ // internal
+ lsp_stream_getchar_t getchar;
+ lsp_bool_t next_available;
+ lsp_uint8_t next_value;
+ lsp_status_t next_status;
+};
+
+struct lsp_out_stream_t {
+ // internal
+ lsp_stream_putchar_t putchar;
+};
+
+typedef struct {
+ lsp_in_stream_t base;
+
+ // internal
+ lsp_mem_t *m;
+ lsp_addr_t str;
+ lsp_uint16_t pos;
+} lsp_str_stream_t;
+
+
+void lsp_in_stream_init(lsp_in_stream_t *s, lsp_stream_getchar_t getchar);
+lsp_status_t lsp_in_stream_read(lsp_in_stream_t *s, lsp_uint8_t *v);
+lsp_status_t lsp_in_stream_peek(lsp_in_stream_t *s, lsp_uint8_t *v);
+
+void lsp_out_stream_init(lsp_out_stream_t *s, lsp_stream_putchar_t putchar);
+lsp_status_t lsp_out_stream_write(lsp_out_stream_t *s, lsp_uint8_t v);
+lsp_status_t lsp_out_stream_write_str(lsp_out_stream_t *s, char *str);
+lsp_status_t lsp_out_stream_write_int(lsp_out_stream_t *s, lsp_int32_t v);
+
+void lsp_str_stream_init(lsp_str_stream_t *s, lsp_mem_t *m, lsp_addr_t str);
+
+#endif
diff --git a/src_c/syntax.c b/src_c/syntax.c
new file mode 100644
index 0000000..5ec6c7e
--- /dev/null
+++ b/src_c/syntax.c
@@ -0,0 +1,192 @@
+#include "syntax.h"
+#include "ctx.h"
+#include "eval.h"
+
+
+lsp_builtin_entry_t lsp_syntaxes[] = {
+ {"lambda", lsp_syntax_lambda}, {"syntax", lsp_syntax_syntax},
+ {"define", lsp_syntax_define}, {"set!", lsp_syntax_set},
+ {"begin", lsp_syntax_begin}, {"quote", lsp_syntax_quote},
+ {"if", lsp_syntax_if}, {NULL, NULL}};
+
+
+lsp_status_t lsp_syntax_lambda(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ if (args == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t fn_args = lsp_mem_get_pair_first(e->m, args);
+ lsp_addr_t fn_body = lsp_mem_get_pair_second(e->m, args);
+
+ if (!lsp_mem_is_symbol(e->m, fn_args) && !lsp_mem_is_pair(e->m, fn_args))
+ return LSP_ERR_ARG_TYPE;
+
+ if (fn_body == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t ctx_copy;
+ lsp_status_t status = lsp_ctx_copy(e->m, ctx, &ctx_copy);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result;
+ status = lsp_mem_create_function(e->m, ctx_copy, fn_args, fn_body, &result);
+ lsp_mem_dec_ref(e->m, ctx_copy);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_syntax_syntax(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ if (args == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t syntax_args = lsp_mem_get_pair_first(e->m, args);
+ lsp_addr_t syntax_body = lsp_mem_get_pair_second(e->m, args);
+
+ if (!lsp_mem_is_symbol(e->m, syntax_args) &&
+ !lsp_mem_is_pair(e->m, syntax_args))
+ return LSP_ERR_ARG_TYPE;
+
+ if (syntax_body == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t ctx_copy;
+ lsp_status_t status = lsp_ctx_copy(e->m, ctx, &ctx_copy);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_addr_t result;
+ status = lsp_mem_create_syntax(e->m, ctx_copy, syntax_args, syntax_body,
+ &result);
+ lsp_mem_dec_ref(e->m, ctx_copy);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_env_set_result_value(e, result);
+ lsp_mem_dec_ref(e->m, result);
+ return status;
+}
+
+
+lsp_status_t lsp_syntax_define(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t symbol;
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &symbol, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_symbol(e->m, symbol))
+ return LSP_ERR_ARG_TYPE;
+
+ status = lsp_env_resolve(e, ctx, value, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_ctx_add(e->m, ctx, symbol, value);
+ lsp_mem_dec_ref(e->m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_syntax_set(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t symbol;
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_2(e->m, args, &symbol, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_symbol(e->m, symbol))
+ return LSP_ERR_ARG_TYPE;
+
+ status = lsp_env_resolve(e, ctx, value, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_ctx_set(e->m, ctx, symbol, value);
+ lsp_mem_dec_ref(e->m, value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_env_set_result_value(e, e->m->nil);
+}
+
+
+lsp_status_t lsp_syntax_begin(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ if (args == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ while (true) {
+ lsp_addr_t value = lsp_mem_get_pair_first(e->m, args);
+ lsp_addr_t next_args = lsp_mem_get_pair_second(e->m, args);
+
+ if (next_args == e->m->nil)
+ return lsp_env_set_result_eval(e, ctx, value);
+
+ lsp_status_t status = lsp_env_resolve(e, ctx, value, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_mem_dec_ref(e->m, value);
+ args = next_args;
+ }
+}
+
+
+lsp_status_t lsp_syntax_quote(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ lsp_addr_t value;
+ lsp_status_t status = lsp_builtin_get_args_1(e->m, args, &value);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_env_set_result_value(e, value);
+}
+
+
+lsp_status_t lsp_syntax_if(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args) {
+ if (args == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t test = lsp_mem_get_pair_first(e->m, args);
+
+ args = lsp_mem_get_pair_second(e->m, args);
+ if (args == e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_addr_t true_value = lsp_mem_get_pair_first(e->m, args);
+
+ lsp_addr_t false_value;
+ args = lsp_mem_get_pair_second(e->m, args);
+ if (args == e->m->nil) {
+ false_value = e->m->nil;
+
+ } else {
+ false_value = lsp_mem_get_pair_first(e->m, args);
+ args = lsp_mem_get_pair_second(e->m, args);
+ }
+
+ if (args != e->m->nil)
+ return LSP_ERR_ARG_COUNT;
+
+ lsp_status_t status = lsp_env_resolve(e, ctx, test, &test);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t is_false = (lsp_mem_is_number(e->m, test) &&
+ lsp_mem_get_number(e->m, test) == 0) ||
+ (lsp_mem_is_pair(e->m, test) && test == e->m->nil) ||
+ (lsp_mem_is_string(e->m, test) &&
+ lsp_mem_get_string_len(e->m, test) == 0) ||
+ (lsp_mem_is_symbol(e->m, test) &&
+ lsp_mem_get_symbol_len(e->m, test) == 0);
+ lsp_mem_dec_ref(e->m, test);
+
+ return lsp_env_set_result_eval(e, ctx,
+ (is_false ? false_value : true_value));
+}
diff --git a/src_c/syntax.h b/src_c/syntax.h
new file mode 100644
index 0000000..55a6516
--- /dev/null
+++ b/src_c/syntax.h
@@ -0,0 +1,17 @@
+#ifndef LISP16_SYNTAX_H
+#define LISP16_SYNTAX_H
+
+#include "builtin.h"
+
+
+extern lsp_builtin_entry_t lsp_syntaxes[];
+
+lsp_status_t lsp_syntax_lambda(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_syntax(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_define(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_set(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_begin(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_quote(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+lsp_status_t lsp_syntax_if(lsp_env_t *e, lsp_addr_t ctx, lsp_addr_t args);
+
+#endif
diff --git a/src_c/write.c b/src_c/write.c
new file mode 100644
index 0000000..2b18aeb
--- /dev/null
+++ b/src_c/write.c
@@ -0,0 +1,174 @@
+#include "write.h"
+#include "function.h"
+#include "syntax.h"
+
+
+static lsp_status_t write_number(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ return lsp_out_stream_write_int(s, lsp_mem_get_number(m, addr));
+}
+
+
+static lsp_status_t write_pair(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_status_t status = lsp_out_stream_write(s, '(');
+ if (status != LSP_SUCCESS)
+ return status;
+
+ lsp_bool_t write_space = false;
+ while (addr != m->nil) {
+ if (write_space) {
+ lsp_out_stream_write(s, ' ');
+ } else {
+ write_space = true;
+ }
+
+ lsp_addr_t first = lsp_mem_get_pair_first(m, addr);
+ lsp_addr_t second = lsp_mem_get_pair_second(m, addr);
+
+ status = lsp_write(m, s, first);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ if (!lsp_mem_is_pair(m, second)) {
+ status = lsp_out_stream_write_str(s, " . ");
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_write(m, s, second);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ break;
+ }
+
+ addr = second;
+ }
+
+ return lsp_out_stream_write(s, ')');
+}
+
+
+static lsp_status_t write_string(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_uint16_t len = lsp_mem_get_string_len(m, addr);
+ lsp_status_t status = lsp_out_stream_write(s, '"');
+
+ for (lsp_uint16_t i = 0; i < len; ++i) {
+ lsp_uint8_t c = lsp_mem_get_string_data(m, addr, i);
+
+ if (c == '"') {
+ status = lsp_out_stream_write(s, '\\');
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ status = lsp_out_stream_write(s, c);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ return lsp_out_stream_write(s, '"');
+}
+
+
+static lsp_status_t write_symbol(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_uint16_t len = lsp_mem_get_symbol_len(m, addr);
+
+ for (lsp_uint16_t i = 0; i < len; ++i) {
+ lsp_uint8_t c = lsp_mem_get_symbol_name(m, addr, i);
+ lsp_status_t status = lsp_out_stream_write(s, c);
+ if (status != LSP_SUCCESS)
+ return status;
+ }
+
+ return LSP_SUCCESS;
+}
+
+
+static lsp_status_t write_builtin_function(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_status_t status = lsp_out_stream_write_str(s, "#<builtin-function-");
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_out_stream_write_str(
+ s, lsp_functions[lsp_mem_get_builtin_index(m, addr)].name);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_out_stream_write(s, '>');
+}
+
+
+static lsp_status_t write_builtin_syntax(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_status_t status = lsp_out_stream_write_str(s, "#<builtin-syntax-");
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_out_stream_write_str(
+ s, lsp_syntaxes[lsp_mem_get_builtin_index(m, addr)].name);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_out_stream_write(s, '>');
+}
+
+
+static lsp_status_t write_function(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_status_t status = lsp_out_stream_write_str(s, "#<function-");
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_out_stream_write_int(s, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_out_stream_write(s, '>');
+}
+
+
+static lsp_status_t write_syntax(lsp_mem_t *m, lsp_out_stream_t *s,
+ lsp_addr_t addr) {
+ lsp_status_t status = lsp_out_stream_write_str(s, "#<syntax-");
+ if (status != LSP_SUCCESS)
+ return status;
+
+ status = lsp_out_stream_write_int(s, addr);
+ if (status != LSP_SUCCESS)
+ return status;
+
+ return lsp_out_stream_write(s, '>');
+}
+
+
+lsp_status_t lsp_write(lsp_mem_t *m, lsp_out_stream_t *s, lsp_addr_t addr) {
+ if (lsp_mem_is_number(m, addr))
+ return write_number(m, s, addr);
+
+ if (lsp_mem_is_pair(m, addr))
+ return write_pair(m, s, addr);
+
+ if (lsp_mem_is_string(m, addr))
+ return write_string(m, s, addr);
+
+ if (lsp_mem_is_symbol(m, addr))
+ return write_symbol(m, s, addr);
+
+ if (lsp_mem_is_builtin_function(m, addr))
+ return write_builtin_function(m, s, addr);
+
+ if (lsp_mem_is_builtin_syntax(m, addr))
+ return write_builtin_syntax(m, s, addr);
+
+ if (lsp_mem_is_function(m, addr))
+ return write_function(m, s, addr);
+
+ if (lsp_mem_is_syntax(m, addr))
+ return write_syntax(m, s, addr);
+
+ return LSP_ERR_WRITE;
+}
diff --git a/src_c/write.h b/src_c/write.h
new file mode 100644
index 0000000..5b7cff1
--- /dev/null
+++ b/src_c/write.h
@@ -0,0 +1,10 @@
+#ifndef LISP16_WRITE_H
+#define LISP16_WRITE_H
+
+#include "mem.h"
+#include "stream.h"
+
+
+lsp_status_t lsp_write(lsp_mem_t *m, lsp_out_stream_t *s, lsp_addr_t addr);
+
+#endif
diff --git a/src_lsp/base-large.lsp b/src_lsp/base-large.lsp
new file mode 100644
index 0000000..e3666d7
--- /dev/null
+++ b/src_lsp/base-large.lsp
@@ -0,0 +1,117 @@
+
+(define nil ())
+(define #f 0)
+(define #t 1)
+
+(define list (lambda x x))
+
+(define car
+ (lambda (x)
+ (apply (lambda (x . rest) x) x)))
+
+(define cdr
+ (lambda (x)
+ (apply (lambda (x . rest) rest) x)))
+
+((lambda (define*)
+ (set! define
+ (syntax (head . rest)
+ (if (pair? head)
+ (list begin
+ (list define* (car head) nil)
+ (list set! (car head)
+ (cons lambda (cons (cdr head) rest))))
+ (cons define* (cons head rest))))))
+ define)
+
+(define (cadr x) (car (cdr x)))
+(define (caar x) (car (car x)))
+(define (cdar x) (cdr (car x)))
+(define (cadar x) (car (cdr (car x))))
+
+(define define-syntax
+ (syntax (head . rest)
+ (list begin
+ (list define (car head) nil)
+ (list set! (car head)
+ (cons syntax (cons (cdr head) rest))))))
+
+(define (not x) (if x #f #t))
+
+(define-syntax (and . args)
+ (if args
+ (list if (car args)
+ (cons and (cdr args))
+ #f)
+ #t))
+
+(define-syntax (or . args)
+ (if args
+ (list if (car args)
+ #t
+ (cons or (cdr args)))
+ #f))
+
+(define (= . args)
+ (if args
+ (if (number? (car args))
+ (or (not (cdr args))
+ (and (eq? (car args) (cadr args))
+ (apply = (cdr args))))
+ #f)
+ #t))
+
+(define (<= . args)
+ (if args
+ (if (number? (car args))
+ (if (cdr args)
+ (and (or (= (car args) (cadr args))
+ (< (car args) (cadr args)))
+ (apply <= (cdr args)))
+ #t)
+ #f)
+ #t))
+
+(define (>= . args)
+ (if args
+ (if (number? (car args))
+ (if (cdr args)
+ (and (or (= (car args) (cadr args))
+ (> (car args) (cadr args)))
+ (apply >= (cdr args)))
+ #t)
+ #f)
+ #t))
+
+(define-syntax (when test . args)
+ (list if test
+ (cons begin args)
+ nil))
+
+(define-syntax (cond . args)
+ (when args
+ (if (eq? (caar args) 'else)
+ (cons begin (cdar args))
+ (list if (caar args)
+ (cons begin (cdar args))
+ (cons cond (cdr args))))))
+
+(define-syntax (let . args)
+ (define (let bindings . body)
+ (if bindings
+ (list
+ (list lambda (list (caar bindings))
+ (apply let (cons (cdr bindings) body)))
+ (cadar bindings))
+ (cons begin body)))
+ (apply let args))
+
+(define (newline) (write-u8 10))
+
+(define (write-string str)
+ (define len (string-length str))
+ (define (write-from i)
+ (when (< i len)
+ (write-u8 (string-ref str i))
+ (write-from (+ i 1))))
+ (write-from 0))
diff --git a/src_lsp/base-small.lsp b/src_lsp/base-small.lsp
new file mode 100644
index 0000000..f0e1e2c
--- /dev/null
+++ b/src_lsp/base-small.lsp
@@ -0,0 +1,6 @@
+
+(define nil ())
+(define #f 0)
+(define #t 1)
+
+(define list (lambda x x))
diff --git a/test.sh b/test.sh
new file mode 100755
index 0000000..0cad28a
--- /dev/null
+++ b/test.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+set -e
+
+cd $(dirname -- "$0")
+
+CC=${CC:-gcc}
+SRC="src_c/arch/posix.c $(find src_c -maxdepth 1 -name '*.c' -a ! -name 'main.c')"
+OUT=build/test
+ARCH=LSP_ARCH_POSIX
+CFLAGS="-O2"
+
+mkdir -p build/test
+
+for i in test_c/*.c; do
+ name=$(basename $i .c)
+ $CC -DLSP_ARCH=$ARCH $CFLAGS -o $OUT/$name $SRC $i
+ $OUT/$name
+done
diff --git a/test_c/acutest.h b/test_c/acutest.h
new file mode 100644
index 0000000..20500ae
--- /dev/null
+++ b/test_c/acutest.h
@@ -0,0 +1,1839 @@
+/*
+ * Acutest -- Another C/C++ Unit Test facility
+ * <https://github.com/mity/acutest>
+ *
+ * Copyright 2013-2020 Martin Mitas
+ * Copyright 2019 Garrett D'Amore
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef ACUTEST_H
+#define ACUTEST_H
+
+
+/************************
+ *** Public interface ***
+ ************************/
+
+/* By default, "acutest.h" provides the main program entry point (function
+ * main()). However, if the test suite is composed of multiple source files
+ * which include "acutest.h", then this causes a problem of multiple main()
+ * definitions. To avoid this problem, #define macro TEST_NO_MAIN in all
+ * compilation units but one.
+ */
+
+/* Macro to specify list of unit tests in the suite.
+ * The unit test implementation MUST provide list of unit tests it implements
+ * with this macro:
+ *
+ * TEST_LIST = {
+ * { "test1_name", test1_func_ptr },
+ * { "test2_name", test2_func_ptr },
+ * ...
+ * { NULL, NULL } // zeroed record marking the end of the list
+ * };
+ *
+ * The list specifies names of each test (must be unique) and pointer to
+ * a function implementing it. The function does not take any arguments
+ * and has no return values, i.e. every test function has to be compatible
+ * with this prototype:
+ *
+ * void test_func(void);
+ *
+ * Note the list has to be ended with a zeroed record.
+ */
+#define TEST_LIST const struct acutest_test_ acutest_list_[]
+
+
+/* Macros for testing whether an unit test succeeds or fails. These macros
+ * can be used arbitrarily in functions implementing the unit tests.
+ *
+ * If any condition fails throughout execution of a test, the test fails.
+ *
+ * TEST_CHECK takes only one argument (the condition), TEST_CHECK_ allows
+ * also to specify an error message to print out if the condition fails.
+ * (It expects printf-like format string and its parameters). The macros
+ * return non-zero (condition passes) or 0 (condition fails).
+ *
+ * That can be useful when more conditions should be checked only if some
+ * preceding condition passes, as illustrated in this code snippet:
+ *
+ * SomeStruct* ptr = allocate_some_struct();
+ * if(TEST_CHECK(ptr != NULL)) {
+ * TEST_CHECK(ptr->member1 < 100);
+ * TEST_CHECK(ptr->member2 > 200);
+ * }
+ */
+#define TEST_CHECK_(cond,...) acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)
+#define TEST_CHECK(cond) acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)
+
+
+/* These macros are the same as TEST_CHECK_ and TEST_CHECK except that if the
+ * condition fails, the currently executed unit test is immediately aborted.
+ *
+ * That is done either by calling abort() if the unit test is executed as a
+ * child process; or via longjmp() if the unit test is executed within the
+ * main Acutest process.
+ *
+ * As a side effect of such abortion, your unit tests may cause memory leaks,
+ * unflushed file descriptors, and other phenomena caused by the abortion.
+ *
+ * Therefore you should not use these as a general replacement for TEST_CHECK.
+ * Use it with some caution, especially if your test causes some other side
+ * effects to the outside world (e.g. communicating with some server, inserting
+ * into a database etc.).
+ */
+#define TEST_ASSERT_(cond,...) \
+ do { \
+ if(!acutest_check_((cond), __FILE__, __LINE__, __VA_ARGS__)) \
+ acutest_abort_(); \
+ } while(0)
+#define TEST_ASSERT(cond) \
+ do { \
+ if(!acutest_check_((cond), __FILE__, __LINE__, "%s", #cond)) \
+ acutest_abort_(); \
+ } while(0)
+
+
+#ifdef __cplusplus
+/* Macros to verify that the code (the 1st argument) throws exception of given
+ * type (the 2nd argument). (Note these macros are only available in C++.)
+ *
+ * TEST_EXCEPTION_ is like TEST_EXCEPTION but accepts custom printf-like
+ * message.
+ *
+ * For example:
+ *
+ * TEST_EXCEPTION(function_that_throw(), ExpectedExceptionType);
+ *
+ * If the function_that_throw() throws ExpectedExceptionType, the check passes.
+ * If the function throws anything incompatible with ExpectedExceptionType
+ * (or if it does not thrown an exception at all), the check fails.
+ */
+#define TEST_EXCEPTION(code, exctype) \
+ do { \
+ bool exc_ok_ = false; \
+ const char *msg_ = NULL; \
+ try { \
+ code; \
+ msg_ = "No exception thrown."; \
+ } catch(exctype const&) { \
+ exc_ok_= true; \
+ } catch(...) { \
+ msg_ = "Unexpected exception thrown."; \
+ } \
+ acutest_check_(exc_ok_, __FILE__, __LINE__, #code " throws " #exctype);\
+ if(msg_ != NULL) \
+ acutest_message_("%s", msg_); \
+ } while(0)
+#define TEST_EXCEPTION_(code, exctype, ...) \
+ do { \
+ bool exc_ok_ = false; \
+ const char *msg_ = NULL; \
+ try { \
+ code; \
+ msg_ = "No exception thrown."; \
+ } catch(exctype const&) { \
+ exc_ok_= true; \
+ } catch(...) { \
+ msg_ = "Unexpected exception thrown."; \
+ } \
+ acutest_check_(exc_ok_, __FILE__, __LINE__, __VA_ARGS__); \
+ if(msg_ != NULL) \
+ acutest_message_("%s", msg_); \
+ } while(0)
+#endif /* #ifdef __cplusplus */
+
+
+/* Sometimes it is useful to split execution of more complex unit tests to some
+ * smaller parts and associate those parts with some names.
+ *
+ * This is especially handy if the given unit test is implemented as a loop
+ * over some vector of multiple testing inputs. Using these macros allow to use
+ * sort of subtitle for each iteration of the loop (e.g. outputting the input
+ * itself or a name associated to it), so that if any TEST_CHECK condition
+ * fails in the loop, it can be easily seen which iteration triggers the
+ * failure, without the need to manually output the iteration-specific data in
+ * every single TEST_CHECK inside the loop body.
+ *
+ * TEST_CASE allows to specify only single string as the name of the case,
+ * TEST_CASE_ provides all the power of printf-like string formatting.
+ *
+ * Note that the test cases cannot be nested. Starting a new test case ends
+ * implicitly the previous one. To end the test case explicitly (e.g. to end
+ * the last test case after exiting the loop), you may use TEST_CASE(NULL).
+ */
+#define TEST_CASE_(...) acutest_case_(__VA_ARGS__)
+#define TEST_CASE(name) acutest_case_("%s", name)
+
+
+/* Maximal output per TEST_CASE call. Longer messages are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_CASE_MAXSIZE
+ #define TEST_CASE_MAXSIZE 64
+#endif
+
+
+/* printf-like macro for outputting an extra information about a failure.
+ *
+ * Intended use is to output some computed output versus the expected value,
+ * e.g. like this:
+ *
+ * if(!TEST_CHECK(produced == expected)) {
+ * TEST_MSG("Expected: %d", expected);
+ * TEST_MSG("Produced: %d", produced);
+ * }
+ *
+ * Note the message is only written down if the most recent use of any checking
+ * macro (like e.g. TEST_CHECK or TEST_EXCEPTION) in the current test failed.
+ * This means the above is equivalent to just this:
+ *
+ * TEST_CHECK(produced == expected);
+ * TEST_MSG("Expected: %d", expected);
+ * TEST_MSG("Produced: %d", produced);
+ *
+ * The macro can deal with multi-line output fairly well. It also automatically
+ * adds a final new-line if there is none present.
+ */
+#define TEST_MSG(...) acutest_message_(__VA_ARGS__)
+
+
+/* Maximal output per TEST_MSG call. Longer messages are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_MSG_MAXSIZE
+ #define TEST_MSG_MAXSIZE 1024
+#endif
+
+
+/* Macro for dumping a block of memory.
+ *
+ * Its intended use is very similar to what TEST_MSG is for, but instead of
+ * generating any printf-like message, this is for dumping raw block of a
+ * memory in a hexadecimal form:
+ *
+ * TEST_CHECK(size_produced == size_expected &&
+ * memcmp(addr_produced, addr_expected, size_produced) == 0);
+ * TEST_DUMP("Expected:", addr_expected, size_expected);
+ * TEST_DUMP("Produced:", addr_produced, size_produced);
+ */
+#define TEST_DUMP(title, addr, size) acutest_dump_(title, addr, size)
+
+/* Maximal output per TEST_DUMP call (in bytes to dump). Longer blocks are cut.
+ * You may define another limit prior including "acutest.h"
+ */
+#ifndef TEST_DUMP_MAXSIZE
+ #define TEST_DUMP_MAXSIZE 1024
+#endif
+
+
+/* Common test initialiation/clean-up
+ *
+ * In some test suites, it may be needed to perform some sort of the same
+ * initialization and/or clean-up in all the tests.
+ *
+ * Such test suites may use macros TEST_INIT and/or TEST_FINI prior including
+ * this header. The expansion of the macro is then used as a body of helper
+ * function called just before executing every single (TEST_INIT) or just after
+ * it ends (TEST_FINI).
+ *
+ * Examples of various ways how to use the macro TEST_INIT:
+ *
+ * #define TEST_INIT my_init_func();
+ * #define TEST_INIT my_init_func() // Works even without the semicolon
+ * #define TEST_INIT setlocale(LC_ALL, NULL);
+ * #define TEST_INIT { setlocale(LC_ALL, NULL); my_init_func(); }
+ *
+ * TEST_FINI is to be used in the same way.
+ */
+
+
+/**********************
+ *** Implementation ***
+ **********************/
+
+/* The unit test files should not rely on anything below. */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+
+#if defined(unix) || defined(__unix__) || defined(__unix) || defined(__APPLE__)
+ #define ACUTEST_UNIX_ 1
+ #include <errno.h>
+ #include <libgen.h>
+ #include <unistd.h>
+ #include <sys/types.h>
+ #include <sys/wait.h>
+ #include <signal.h>
+ #include <time.h>
+
+ #if defined CLOCK_PROCESS_CPUTIME_ID && defined CLOCK_MONOTONIC
+ #define ACUTEST_HAS_POSIX_TIMER_ 1
+ #endif
+#endif
+
+#if defined(_gnu_linux_) || defined(__linux__)
+ #define ACUTEST_LINUX_ 1
+ #include <fcntl.h>
+ #include <sys/stat.h>
+#endif
+
+#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
+ #define ACUTEST_WIN_ 1
+ #include <windows.h>
+ #include <io.h>
+#endif
+
+#if defined(__APPLE__)
+ #define ACUTEST_MACOS_
+ #include <assert.h>
+ #include <stdbool.h>
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <sys/sysctl.h>
+#endif
+
+#ifdef __cplusplus
+ #include <exception>
+#endif
+
+#ifdef __has_include
+ #if __has_include(<valgrind.h>)
+ #include <valgrind.h>
+ #endif
+#endif
+
+/* Enable the use of the non-standard keyword __attribute__ to silence warnings under some compilers */
+#if defined(__GNUC__) || defined(__clang__)
+ #define ACUTEST_ATTRIBUTE_(attr) __attribute__((attr))
+#else
+ #define ACUTEST_ATTRIBUTE_(attr)
+#endif
+
+/* Note our global private identifiers end with '_' to mitigate risk of clash
+ * with the unit tests implementation. */
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifdef _MSC_VER
+ /* In the multi-platform code like ours, we cannot use the non-standard
+ * "safe" functions from Microsoft C lib like e.g. sprintf_s() instead of
+ * standard sprintf(). Hence, lets disable the warning C4996. */
+ #pragma warning(push)
+ #pragma warning(disable: 4996)
+#endif
+
+
+struct acutest_test_ {
+ const char* name;
+ void (*func)(void);
+};
+
+struct acutest_test_data_ {
+ unsigned char flags;
+ double duration;
+};
+
+enum {
+ ACUTEST_FLAG_RUN_ = 1 << 0,
+ ACUTEST_FLAG_SUCCESS_ = 1 << 1,
+ ACUTEST_FLAG_FAILURE_ = 1 << 2,
+};
+
+extern const struct acutest_test_ acutest_list_[];
+
+int acutest_check_(int cond, const char* file, int line, const char* fmt, ...);
+void acutest_case_(const char* fmt, ...);
+void acutest_message_(const char* fmt, ...);
+void acutest_dump_(const char* title, const void* addr, size_t size);
+void acutest_abort_(void) ACUTEST_ATTRIBUTE_(noreturn);
+
+
+#ifndef TEST_NO_MAIN
+
+static char* acutest_argv0_ = NULL;
+static size_t acutest_list_size_ = 0;
+static struct acutest_test_data_* acutest_test_data_ = NULL;
+static size_t acutest_count_ = 0;
+static int acutest_no_exec_ = -1;
+static int acutest_no_summary_ = 0;
+static int acutest_tap_ = 0;
+static int acutest_skip_mode_ = 0;
+static int acutest_worker_ = 0;
+static int acutest_worker_index_ = 0;
+static int acutest_cond_failed_ = 0;
+static int acutest_was_aborted_ = 0;
+static FILE *acutest_xml_output_ = NULL;
+
+static int acutest_stat_failed_units_ = 0;
+static int acutest_stat_run_units_ = 0;
+
+static const struct acutest_test_* acutest_current_test_ = NULL;
+static int acutest_current_index_ = 0;
+static char acutest_case_name_[TEST_CASE_MAXSIZE] = "";
+static int acutest_test_already_logged_ = 0;
+static int acutest_case_already_logged_ = 0;
+static int acutest_verbose_level_ = 2;
+static int acutest_test_failures_ = 0;
+static int acutest_colorize_ = 0;
+static int acutest_timer_ = 0;
+
+static int acutest_abort_has_jmp_buf_ = 0;
+static jmp_buf acutest_abort_jmp_buf_;
+
+
+static void
+acutest_cleanup_(void)
+{
+ free((void*) acutest_test_data_);
+}
+
+static void ACUTEST_ATTRIBUTE_(noreturn)
+acutest_exit_(int exit_code)
+{
+ acutest_cleanup_();
+ exit(exit_code);
+}
+
+#if defined ACUTEST_WIN_
+ typedef LARGE_INTEGER acutest_timer_type_;
+ static LARGE_INTEGER acutest_timer_freq_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ static void
+ acutest_timer_init_(void)
+ {
+ QueryPerformanceFrequency(&acutest_timer_freq_);
+ }
+
+ static void
+ acutest_timer_get_time_(LARGE_INTEGER* ts)
+ {
+ QueryPerformanceCounter(ts);
+ }
+
+ static double
+ acutest_timer_diff_(LARGE_INTEGER start, LARGE_INTEGER end)
+ {
+ double duration = (double)(end.QuadPart - start.QuadPart);
+ duration /= (double)acutest_timer_freq_.QuadPart;
+ return duration;
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {
+ printf("%.6lf secs", acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_));
+ }
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ static clockid_t acutest_timer_id_;
+ typedef struct timespec acutest_timer_type_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ static void
+ acutest_timer_init_(void)
+ {
+ if(acutest_timer_ == 1)
+ acutest_timer_id_ = CLOCK_MONOTONIC;
+ else if(acutest_timer_ == 2)
+ acutest_timer_id_ = CLOCK_PROCESS_CPUTIME_ID;
+ }
+
+ static void
+ acutest_timer_get_time_(struct timespec* ts)
+ {
+ clock_gettime(acutest_timer_id_, ts);
+ }
+
+ static double
+ acutest_timer_diff_(struct timespec start, struct timespec end)
+ {
+ double endns;
+ double startns;
+
+ endns = end.tv_sec;
+ endns *= 1e9;
+ endns += end.tv_nsec;
+
+ startns = start.tv_sec;
+ startns *= 1e9;
+ startns += start.tv_nsec;
+
+ return ((endns - startns)/ 1e9);
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {
+ printf("%.6lf secs",
+ acutest_timer_diff_(acutest_timer_start_, acutest_timer_end_));
+ }
+#else
+ typedef int acutest_timer_type_;
+ static acutest_timer_type_ acutest_timer_start_;
+ static acutest_timer_type_ acutest_timer_end_;
+
+ void
+ acutest_timer_init_(void)
+ {}
+
+ static void
+ acutest_timer_get_time_(int* ts)
+ {
+ (void) ts;
+ }
+
+ static double
+ acutest_timer_diff_(int start, int end)
+ {
+ (void) start;
+ (void) end;
+ return 0.0;
+ }
+
+ static void
+ acutest_timer_print_diff_(void)
+ {}
+#endif
+
+#define ACUTEST_COLOR_DEFAULT_ 0
+#define ACUTEST_COLOR_GREEN_ 1
+#define ACUTEST_COLOR_RED_ 2
+#define ACUTEST_COLOR_DEFAULT_INTENSIVE_ 3
+#define ACUTEST_COLOR_GREEN_INTENSIVE_ 4
+#define ACUTEST_COLOR_RED_INTENSIVE_ 5
+
+static int ACUTEST_ATTRIBUTE_(format (printf, 2, 3))
+acutest_colored_printf_(int color, const char* fmt, ...)
+{
+ va_list args;
+ char buffer[256];
+ int n;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+ buffer[sizeof(buffer)-1] = '\0';
+
+ if(!acutest_colorize_) {
+ return printf("%s", buffer);
+ }
+
+#if defined ACUTEST_UNIX_
+ {
+ const char* col_str;
+ switch(color) {
+ case ACUTEST_COLOR_GREEN_: col_str = "\033[0;32m"; break;
+ case ACUTEST_COLOR_RED_: col_str = "\033[0;31m"; break;
+ case ACUTEST_COLOR_GREEN_INTENSIVE_: col_str = "\033[1;32m"; break;
+ case ACUTEST_COLOR_RED_INTENSIVE_: col_str = "\033[1;31m"; break;
+ case ACUTEST_COLOR_DEFAULT_INTENSIVE_: col_str = "\033[1m"; break;
+ default: col_str = "\033[0m"; break;
+ }
+ printf("%s", col_str);
+ n = printf("%s", buffer);
+ printf("\033[0m");
+ return n;
+ }
+#elif defined ACUTEST_WIN_
+ {
+ HANDLE h;
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ WORD attr;
+
+ h = GetStdHandle(STD_OUTPUT_HANDLE);
+ GetConsoleScreenBufferInfo(h, &info);
+
+ switch(color) {
+ case ACUTEST_COLOR_GREEN_: attr = FOREGROUND_GREEN; break;
+ case ACUTEST_COLOR_RED_: attr = FOREGROUND_RED; break;
+ case ACUTEST_COLOR_GREEN_INTENSIVE_: attr = FOREGROUND_GREEN | FOREGROUND_INTENSITY; break;
+ case ACUTEST_COLOR_RED_INTENSIVE_: attr = FOREGROUND_RED | FOREGROUND_INTENSITY; break;
+ case ACUTEST_COLOR_DEFAULT_INTENSIVE_: attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY; break;
+ default: attr = 0; break;
+ }
+ if(attr != 0)
+ SetConsoleTextAttribute(h, attr);
+ n = printf("%s", buffer);
+ SetConsoleTextAttribute(h, info.wAttributes);
+ return n;
+ }
+#else
+ n = printf("%s", buffer);
+ return n;
+#endif
+}
+
+static void
+acutest_begin_test_line_(const struct acutest_test_* test)
+{
+ if(!acutest_tap_) {
+ if(acutest_verbose_level_ >= 3) {
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s:\n", test->name);
+ acutest_test_already_logged_++;
+ } else if(acutest_verbose_level_ >= 1) {
+ int n;
+ char spaces[48];
+
+ n = acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Test %s... ", test->name);
+ memset(spaces, ' ', sizeof(spaces));
+ if(n < (int) sizeof(spaces))
+ printf("%.*s", (int) sizeof(spaces) - n, spaces);
+ } else {
+ acutest_test_already_logged_ = 1;
+ }
+ }
+}
+
+static void
+acutest_finish_test_line_(int result)
+{
+ if(acutest_tap_) {
+ const char* str = (result == 0) ? "ok" : "not ok";
+
+ printf("%s %d - %s\n", str, acutest_current_index_ + 1, acutest_current_test_->name);
+
+ if(result == 0 && acutest_timer_) {
+ printf("# Duration: ");
+ acutest_timer_print_diff_();
+ printf("\n");
+ }
+ } else {
+ int color = (result == 0) ? ACUTEST_COLOR_GREEN_INTENSIVE_ : ACUTEST_COLOR_RED_INTENSIVE_;
+ const char* str = (result == 0) ? "OK" : "FAILED";
+ printf("[ ");
+ acutest_colored_printf_(color, "%s", str);
+ printf(" ]");
+
+ if(result == 0 && acutest_timer_) {
+ printf(" ");
+ acutest_timer_print_diff_();
+ }
+
+ printf("\n");
+ }
+}
+
+static void
+acutest_line_indent_(int level)
+{
+ static const char spaces[] = " ";
+ int n = level * 2;
+
+ if(acutest_tap_ && n > 0) {
+ n--;
+ printf("#");
+ }
+
+ while(n > 16) {
+ printf("%s", spaces);
+ n -= 16;
+ }
+ printf("%.*s", n, spaces);
+}
+
+int ACUTEST_ATTRIBUTE_(format (printf, 4, 5))
+acutest_check_(int cond, const char* file, int line, const char* fmt, ...)
+{
+ const char *result_str;
+ int result_color;
+ int verbose_level;
+
+ if(cond) {
+ result_str = "ok";
+ result_color = ACUTEST_COLOR_GREEN_;
+ verbose_level = 3;
+ } else {
+ if(!acutest_test_already_logged_ && acutest_current_test_ != NULL)
+ acutest_finish_test_line_(-1);
+
+ result_str = "failed";
+ result_color = ACUTEST_COLOR_RED_;
+ verbose_level = 2;
+ acutest_test_failures_++;
+ acutest_test_already_logged_++;
+ }
+
+ if(acutest_verbose_level_ >= verbose_level) {
+ va_list args;
+
+ if(!acutest_case_already_logged_ && acutest_case_name_[0]) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_);
+ acutest_test_already_logged_++;
+ acutest_case_already_logged_++;
+ }
+
+ acutest_line_indent_(acutest_case_name_[0] ? 2 : 1);
+ if(file != NULL) {
+#ifdef ACUTEST_WIN_
+ const char* lastsep1 = strrchr(file, '\\');
+ const char* lastsep2 = strrchr(file, '/');
+ if(lastsep1 == NULL)
+ lastsep1 = file-1;
+ if(lastsep2 == NULL)
+ lastsep2 = file-1;
+ file = (lastsep1 > lastsep2 ? lastsep1 : lastsep2) + 1;
+#else
+ const char* lastsep = strrchr(file, '/');
+ if(lastsep != NULL)
+ file = lastsep+1;
+#endif
+ printf("%s:%d: Check ", file, line);
+ }
+
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+
+ printf("... ");
+ acutest_colored_printf_(result_color, "%s", result_str);
+ printf("\n");
+ acutest_test_already_logged_++;
+ }
+
+ acutest_cond_failed_ = (cond == 0);
+ return !acutest_cond_failed_;
+}
+
+void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_case_(const char* fmt, ...)
+{
+ va_list args;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ if(acutest_case_name_[0]) {
+ acutest_case_already_logged_ = 0;
+ acutest_case_name_[0] = '\0';
+ }
+
+ if(fmt == NULL)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(acutest_case_name_, sizeof(acutest_case_name_) - 1, fmt, args);
+ va_end(args);
+ acutest_case_name_[sizeof(acutest_case_name_) - 1] = '\0';
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Case %s:\n", acutest_case_name_);
+ acutest_test_already_logged_++;
+ acutest_case_already_logged_++;
+ }
+}
+
+void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_message_(const char* fmt, ...)
+{
+ char buffer[TEST_MSG_MAXSIZE];
+ char* line_beg;
+ char* line_end;
+ va_list args;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ /* We allow extra message only when something is already wrong in the
+ * current test. */
+ if(acutest_current_test_ == NULL || !acutest_cond_failed_)
+ return;
+
+ va_start(args, fmt);
+ vsnprintf(buffer, TEST_MSG_MAXSIZE, fmt, args);
+ va_end(args);
+ buffer[TEST_MSG_MAXSIZE-1] = '\0';
+
+ line_beg = buffer;
+ while(1) {
+ line_end = strchr(line_beg, '\n');
+ if(line_end == NULL)
+ break;
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf("%.*s\n", (int)(line_end - line_beg), line_beg);
+ line_beg = line_end + 1;
+ }
+ if(line_beg[0] != '\0') {
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf("%s\n", line_beg);
+ }
+}
+
+void
+acutest_dump_(const char* title, const void* addr, size_t size)
+{
+ static const size_t BYTES_PER_LINE = 16;
+ size_t line_beg;
+ size_t truncate = 0;
+
+ if(acutest_verbose_level_ < 2)
+ return;
+
+ /* We allow extra message only when something is already wrong in the
+ * current test. */
+ if(acutest_current_test_ == NULL || !acutest_cond_failed_)
+ return;
+
+ if(size > TEST_DUMP_MAXSIZE) {
+ truncate = size - TEST_DUMP_MAXSIZE;
+ size = TEST_DUMP_MAXSIZE;
+ }
+
+ acutest_line_indent_(acutest_case_name_[0] ? 3 : 2);
+ printf((title[strlen(title)-1] == ':') ? "%s\n" : "%s:\n", title);
+
+ for(line_beg = 0; line_beg < size; line_beg += BYTES_PER_LINE) {
+ size_t line_end = line_beg + BYTES_PER_LINE;
+ size_t off;
+
+ acutest_line_indent_(acutest_case_name_[0] ? 4 : 3);
+ printf("%08lx: ", (unsigned long)line_beg);
+ for(off = line_beg; off < line_end; off++) {
+ if(off < size)
+ printf(" %02x", ((const unsigned char*)addr)[off]);
+ else
+ printf(" ");
+ }
+
+ printf(" ");
+ for(off = line_beg; off < line_end; off++) {
+ unsigned char byte = ((const unsigned char*)addr)[off];
+ if(off < size)
+ printf("%c", (iscntrl(byte) ? '.' : byte));
+ else
+ break;
+ }
+
+ printf("\n");
+ }
+
+ if(truncate > 0) {
+ acutest_line_indent_(acutest_case_name_[0] ? 4 : 3);
+ printf(" ... (and more %u bytes)\n", (unsigned) truncate);
+ }
+}
+
+/* This is called just before each test */
+static void
+acutest_init_(const char *test_name)
+{
+#ifdef TEST_INIT
+ TEST_INIT
+ ; /* Allow for a single unterminated function call */
+#endif
+
+ /* Suppress any warnings about unused variable. */
+ (void) test_name;
+}
+
+/* This is called after each test */
+static void
+acutest_fini_(const char *test_name)
+{
+#ifdef TEST_FINI
+ TEST_FINI
+ ; /* Allow for a single unterminated function call */
+#endif
+
+ /* Suppress any warnings about unused variable. */
+ (void) test_name;
+}
+
+void
+acutest_abort_(void)
+{
+ if(acutest_abort_has_jmp_buf_) {
+ longjmp(acutest_abort_jmp_buf_, 1);
+ } else {
+ if(acutest_current_test_ != NULL)
+ acutest_fini_(acutest_current_test_->name);
+ abort();
+ }
+}
+
+static void
+acutest_list_names_(void)
+{
+ const struct acutest_test_* test;
+
+ printf("Unit tests:\n");
+ for(test = &acutest_list_[0]; test->func != NULL; test++)
+ printf(" %s\n", test->name);
+}
+
+static void
+acutest_remember_(int i)
+{
+ if(acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_)
+ return;
+
+ acutest_test_data_[i].flags |= ACUTEST_FLAG_RUN_;
+ acutest_count_++;
+}
+
+static void
+acutest_set_success_(int i, int success)
+{
+ acutest_test_data_[i].flags |= success ? ACUTEST_FLAG_SUCCESS_ : ACUTEST_FLAG_FAILURE_;
+}
+
+static void
+acutest_set_duration_(int i, double duration)
+{
+ acutest_test_data_[i].duration = duration;
+}
+
+static int
+acutest_name_contains_word_(const char* name, const char* pattern)
+{
+ static const char word_delim[] = " \t-_/.,:;";
+ const char* substr;
+ size_t pattern_len;
+
+ pattern_len = strlen(pattern);
+
+ substr = strstr(name, pattern);
+ while(substr != NULL) {
+ int starts_on_word_boundary = (substr == name || strchr(word_delim, substr[-1]) != NULL);
+ int ends_on_word_boundary = (substr[pattern_len] == '\0' || strchr(word_delim, substr[pattern_len]) != NULL);
+
+ if(starts_on_word_boundary && ends_on_word_boundary)
+ return 1;
+
+ substr = strstr(substr+1, pattern);
+ }
+
+ return 0;
+}
+
+static int
+acutest_lookup_(const char* pattern)
+{
+ int i;
+ int n = 0;
+
+ /* Try exact match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(strcmp(acutest_list_[i].name, pattern) == 0) {
+ acutest_remember_(i);
+ n++;
+ break;
+ }
+ }
+ if(n > 0)
+ return n;
+
+ /* Try word match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(acutest_name_contains_word_(acutest_list_[i].name, pattern)) {
+ acutest_remember_(i);
+ n++;
+ }
+ }
+ if(n > 0)
+ return n;
+
+ /* Try relaxed match. */
+ for(i = 0; i < (int) acutest_list_size_; i++) {
+ if(strstr(acutest_list_[i].name, pattern) != NULL) {
+ acutest_remember_(i);
+ n++;
+ }
+ }
+
+ return n;
+}
+
+
+/* Called if anything goes bad in Acutest, or if the unit test ends in other
+ * way then by normal returning from its function (e.g. exception or some
+ * abnormal child process termination). */
+static void ACUTEST_ATTRIBUTE_(format (printf, 1, 2))
+acutest_error_(const char* fmt, ...)
+{
+ if(acutest_verbose_level_ == 0)
+ return;
+
+ if(acutest_verbose_level_ >= 2) {
+ va_list args;
+
+ acutest_line_indent_(1);
+ if(acutest_verbose_level_ >= 3)
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "ERROR: ");
+ va_start(args, fmt);
+ vprintf(fmt, args);
+ va_end(args);
+ printf("\n");
+ }
+
+ if(acutest_verbose_level_ >= 3) {
+ printf("\n");
+ }
+}
+
+/* Call directly the given test unit function. */
+static int
+acutest_do_run_(const struct acutest_test_* test, int index)
+{
+ int status = -1;
+
+ acutest_was_aborted_ = 0;
+ acutest_current_test_ = test;
+ acutest_current_index_ = index;
+ acutest_test_failures_ = 0;
+ acutest_test_already_logged_ = 0;
+ acutest_cond_failed_ = 0;
+
+#ifdef __cplusplus
+ try {
+#endif
+ acutest_init_(test->name);
+ acutest_begin_test_line_(test);
+
+ /* This is good to do in case the test unit crashes. */
+ fflush(stdout);
+ fflush(stderr);
+
+ if(!acutest_worker_) {
+ acutest_abort_has_jmp_buf_ = 1;
+ if(setjmp(acutest_abort_jmp_buf_) != 0) {
+ acutest_was_aborted_ = 1;
+ goto aborted;
+ }
+ }
+
+ acutest_timer_get_time_(&acutest_timer_start_);
+ test->func();
+aborted:
+ acutest_abort_has_jmp_buf_ = 0;
+ acutest_timer_get_time_(&acutest_timer_end_);
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ if(acutest_test_failures_ == 0) {
+ acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS: ");
+ printf("All conditions have passed.\n");
+
+ if(acutest_timer_) {
+ acutest_line_indent_(1);
+ printf("Duration: ");
+ acutest_timer_print_diff_();
+ printf("\n");
+ }
+ } else {
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ if(!acutest_was_aborted_) {
+ printf("%d condition%s %s failed.\n",
+ acutest_test_failures_,
+ (acutest_test_failures_ == 1) ? "" : "s",
+ (acutest_test_failures_ == 1) ? "has" : "have");
+ } else {
+ printf("Aborted.\n");
+ }
+ }
+ printf("\n");
+ } else if(acutest_verbose_level_ >= 1 && acutest_test_failures_ == 0) {
+ acutest_finish_test_line_(0);
+ }
+
+ status = (acutest_test_failures_ == 0) ? 0 : -1;
+
+#ifdef __cplusplus
+ } catch(std::exception& e) {
+ const char* what = e.what();
+ acutest_check_(0, NULL, 0, "Threw std::exception");
+ if(what != NULL)
+ acutest_message_("std::exception::what(): %s", what);
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ printf("C++ exception.\n\n");
+ }
+ } catch(...) {
+ acutest_check_(0, NULL, 0, "Threw an exception");
+
+ if(acutest_verbose_level_ >= 3) {
+ acutest_line_indent_(1);
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED: ");
+ printf("C++ exception.\n\n");
+ }
+ }
+#endif
+
+ acutest_fini_(test->name);
+ acutest_case_(NULL);
+ acutest_current_test_ = NULL;
+
+ return status;
+}
+
+/* Trigger the unit test. If possible (and not suppressed) it starts a child
+ * process who calls acutest_do_run_(), otherwise it calls acutest_do_run_()
+ * directly. */
+static void
+acutest_run_(const struct acutest_test_* test, int index, int master_index)
+{
+ int failed = 1;
+ acutest_timer_type_ start, end;
+
+ acutest_current_test_ = test;
+ acutest_test_already_logged_ = 0;
+ acutest_timer_get_time_(&start);
+
+ if(!acutest_no_exec_) {
+
+#if defined(ACUTEST_UNIX_)
+
+ pid_t pid;
+ int exit_code;
+
+ /* Make sure the child starts with empty I/O buffers. */
+ fflush(stdout);
+ fflush(stderr);
+
+ pid = fork();
+ if(pid == (pid_t)-1) {
+ acutest_error_("Cannot fork. %s [%d]", strerror(errno), errno);
+ failed = 1;
+ } else if(pid == 0) {
+ /* Child: Do the test. */
+ acutest_worker_ = 1;
+ failed = (acutest_do_run_(test, index) != 0);
+ acutest_exit_(failed ? 1 : 0);
+ } else {
+ /* Parent: Wait until child terminates and analyze its exit code. */
+ waitpid(pid, &exit_code, 0);
+ if(WIFEXITED(exit_code)) {
+ switch(WEXITSTATUS(exit_code)) {
+ case 0: failed = 0; break; /* test has passed. */
+ case 1: /* noop */ break; /* "normal" failure. */
+ default: acutest_error_("Unexpected exit code [%d]", WEXITSTATUS(exit_code));
+ }
+ } else if(WIFSIGNALED(exit_code)) {
+ char tmp[32];
+ const char* signame;
+ switch(WTERMSIG(exit_code)) {
+ case SIGINT: signame = "SIGINT"; break;
+ case SIGHUP: signame = "SIGHUP"; break;
+ case SIGQUIT: signame = "SIGQUIT"; break;
+ case SIGABRT: signame = "SIGABRT"; break;
+ case SIGKILL: signame = "SIGKILL"; break;
+ case SIGSEGV: signame = "SIGSEGV"; break;
+ case SIGILL: signame = "SIGILL"; break;
+ case SIGTERM: signame = "SIGTERM"; break;
+ default: sprintf(tmp, "signal %d", WTERMSIG(exit_code)); signame = tmp; break;
+ }
+ acutest_error_("Test interrupted by %s.", signame);
+ } else {
+ acutest_error_("Test ended in an unexpected way [%d].", exit_code);
+ }
+ }
+
+#elif defined(ACUTEST_WIN_)
+
+ char buffer[512] = {0};
+ STARTUPINFOA startupInfo;
+ PROCESS_INFORMATION processInfo;
+ DWORD exitCode;
+
+ /* Windows has no fork(). So we propagate all info into the child
+ * through a command line arguments. */
+ _snprintf(buffer, sizeof(buffer)-1,
+ "%s --worker=%d %s --no-exec --no-summary %s --verbose=%d --color=%s -- \"%s\"",
+ acutest_argv0_, index, acutest_timer_ ? "--time" : "",
+ acutest_tap_ ? "--tap" : "", acutest_verbose_level_,
+ acutest_colorize_ ? "always" : "never",
+ test->name);
+ memset(&startupInfo, 0, sizeof(startupInfo));
+ startupInfo.cb = sizeof(STARTUPINFO);
+ if(CreateProcessA(NULL, buffer, NULL, NULL, FALSE, 0, NULL, NULL, &startupInfo, &processInfo)) {
+ WaitForSingleObject(processInfo.hProcess, INFINITE);
+ GetExitCodeProcess(processInfo.hProcess, &exitCode);
+ CloseHandle(processInfo.hThread);
+ CloseHandle(processInfo.hProcess);
+ failed = (exitCode != 0);
+ if(exitCode > 1) {
+ switch(exitCode) {
+ case 3: acutest_error_("Aborted."); break;
+ case 0xC0000005: acutest_error_("Access violation."); break;
+ default: acutest_error_("Test ended in an unexpected way [%lu].", exitCode); break;
+ }
+ }
+ } else {
+ acutest_error_("Cannot create unit test subprocess [%ld].", GetLastError());
+ failed = 1;
+ }
+
+#else
+
+ /* A platform where we don't know how to run child process. */
+ failed = (acutest_do_run_(test, index) != 0);
+
+#endif
+
+ } else {
+ /* Child processes suppressed through --no-exec. */
+ failed = (acutest_do_run_(test, index) != 0);
+ }
+ acutest_timer_get_time_(&end);
+
+ acutest_current_test_ = NULL;
+
+ acutest_stat_run_units_++;
+ if(failed)
+ acutest_stat_failed_units_++;
+
+ acutest_set_success_(master_index, !failed);
+ acutest_set_duration_(master_index, acutest_timer_diff_(start, end));
+}
+
+#if defined(ACUTEST_WIN_)
+/* Callback for SEH events. */
+static LONG CALLBACK
+acutest_seh_exception_filter_(EXCEPTION_POINTERS *ptrs)
+{
+ acutest_check_(0, NULL, 0, "Unhandled SEH exception");
+ acutest_message_("Exception code: 0x%08lx", ptrs->ExceptionRecord->ExceptionCode);
+ acutest_message_("Exception address: 0x%p", ptrs->ExceptionRecord->ExceptionAddress);
+
+ fflush(stdout);
+ fflush(stderr);
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+#endif
+
+
+#define ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ 0x0001
+#define ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ 0x0002
+
+#define ACUTEST_CMDLINE_OPTID_NONE_ 0
+#define ACUTEST_CMDLINE_OPTID_UNKNOWN_ (-0x7fffffff + 0)
+#define ACUTEST_CMDLINE_OPTID_MISSINGARG_ (-0x7fffffff + 1)
+#define ACUTEST_CMDLINE_OPTID_BOGUSARG_ (-0x7fffffff + 2)
+
+typedef struct acutest_test_CMDLINE_OPTION_ {
+ char shortname;
+ const char* longname;
+ int id;
+ unsigned flags;
+} ACUTEST_CMDLINE_OPTION_;
+
+static int
+acutest_cmdline_handle_short_opt_group_(const ACUTEST_CMDLINE_OPTION_* options,
+ const char* arggroup,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+ const ACUTEST_CMDLINE_OPTION_* opt;
+ int i;
+ int ret = 0;
+
+ for(i = 0; arggroup[i] != '\0'; i++) {
+ for(opt = options; opt->id != 0; opt++) {
+ if(arggroup[i] == opt->shortname)
+ break;
+ }
+
+ if(opt->id != 0 && !(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) {
+ ret = callback(opt->id, NULL);
+ } else {
+ /* Unknown option. */
+ char badoptname[3];
+ badoptname[0] = '-';
+ badoptname[1] = arggroup[i];
+ badoptname[2] = '\0';
+ ret = callback((opt->id != 0 ? ACUTEST_CMDLINE_OPTID_MISSINGARG_ : ACUTEST_CMDLINE_OPTID_UNKNOWN_),
+ badoptname);
+ }
+
+ if(ret != 0)
+ break;
+ }
+
+ return ret;
+}
+
+#define ACUTEST_CMDLINE_AUXBUF_SIZE_ 32
+
+static int
+acutest_cmdline_read_(const ACUTEST_CMDLINE_OPTION_* options, int argc, char** argv,
+ int (*callback)(int /*optval*/, const char* /*arg*/))
+{
+
+ const ACUTEST_CMDLINE_OPTION_* opt;
+ char auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_+1];
+ int after_doubledash = 0;
+ int i = 1;
+ int ret = 0;
+
+ auxbuf[ACUTEST_CMDLINE_AUXBUF_SIZE_] = '\0';
+
+ while(i < argc) {
+ if(after_doubledash || strcmp(argv[i], "-") == 0) {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else if(strcmp(argv[i], "--") == 0) {
+ /* End of options. All the remaining members are non-option arguments. */
+ after_doubledash = 1;
+ } else if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else {
+ for(opt = options; opt->id != 0; opt++) {
+ if(opt->longname != NULL && strncmp(argv[i], "--", 2) == 0) {
+ size_t len = strlen(opt->longname);
+ if(strncmp(argv[i]+2, opt->longname, len) == 0) {
+ /* Regular long option. */
+ if(argv[i][2+len] == '\0') {
+ /* with no argument provided. */
+ if(!(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_))
+ ret = callback(opt->id, NULL);
+ else
+ ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]);
+ break;
+ } else if(argv[i][2+len] == '=') {
+ /* with an argument provided. */
+ if(opt->flags & (ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ | ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_)) {
+ ret = callback(opt->id, argv[i]+2+len+1);
+ } else {
+ sprintf(auxbuf, "--%s", opt->longname);
+ ret = callback(ACUTEST_CMDLINE_OPTID_BOGUSARG_, auxbuf);
+ }
+ break;
+ } else {
+ continue;
+ }
+ }
+ } else if(opt->shortname != '\0' && argv[i][0] == '-') {
+ if(argv[i][1] == opt->shortname) {
+ /* Regular short option. */
+ if(opt->flags & ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_) {
+ if(argv[i][2] != '\0')
+ ret = callback(opt->id, argv[i]+2);
+ else if(i+1 < argc)
+ ret = callback(opt->id, argv[++i]);
+ else
+ ret = callback(ACUTEST_CMDLINE_OPTID_MISSINGARG_, argv[i]);
+ break;
+ } else {
+ ret = callback(opt->id, NULL);
+
+ /* There might be more (argument-less) short options
+ * grouped together. */
+ if(ret == 0 && argv[i][2] != '\0')
+ ret = acutest_cmdline_handle_short_opt_group_(options, argv[i]+2, callback);
+ break;
+ }
+ }
+ }
+ }
+
+ if(opt->id == 0) { /* still not handled? */
+ if(argv[i][0] != '-') {
+ /* Non-option argument. */
+ ret = callback(ACUTEST_CMDLINE_OPTID_NONE_, argv[i]);
+ } else {
+ /* Unknown option. */
+ char* badoptname = argv[i];
+
+ if(strncmp(badoptname, "--", 2) == 0) {
+ /* Strip any argument from the long option. */
+ char* assignment = strchr(badoptname, '=');
+ if(assignment != NULL) {
+ size_t len = assignment - badoptname;
+ if(len > ACUTEST_CMDLINE_AUXBUF_SIZE_)
+ len = ACUTEST_CMDLINE_AUXBUF_SIZE_;
+ strncpy(auxbuf, badoptname, len);
+ auxbuf[len] = '\0';
+ badoptname = auxbuf;
+ }
+ }
+
+ ret = callback(ACUTEST_CMDLINE_OPTID_UNKNOWN_, badoptname);
+ }
+ }
+ }
+
+ if(ret != 0)
+ return ret;
+ i++;
+ }
+
+ return ret;
+}
+
+static void
+acutest_help_(void)
+{
+ printf("Usage: %s [options] [test...]\n", acutest_argv0_);
+ printf("\n");
+ printf("Run the specified unit tests; or if the option '--skip' is used, run all\n");
+ printf("tests in the suite but those listed. By default, if no tests are specified\n");
+ printf("on the command line, all unit tests in the suite are run.\n");
+ printf("\n");
+ printf("Options:\n");
+ printf(" -s, --skip Execute all unit tests but the listed ones\n");
+ printf(" --exec[=WHEN] If supported, execute unit tests as child processes\n");
+ printf(" (WHEN is one of 'auto', 'always', 'never')\n");
+ printf(" -E, --no-exec Same as --exec=never\n");
+#if defined ACUTEST_WIN_
+ printf(" -t, --time Measure test duration\n");
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ printf(" -t, --time Measure test duration (real time)\n");
+ printf(" --time=TIMER Measure test duration, using given timer\n");
+ printf(" (TIMER is one of 'real', 'cpu')\n");
+#endif
+ printf(" --no-summary Suppress printing of test results summary\n");
+ printf(" --tap Produce TAP-compliant output\n");
+ printf(" (See https://testanything.org/)\n");
+ printf(" -x, --xml-output=FILE Enable XUnit output to the given file\n");
+ printf(" -l, --list List unit tests in the suite and exit\n");
+ printf(" -v, --verbose Make output more verbose\n");
+ printf(" --verbose=LEVEL Set verbose level to LEVEL:\n");
+ printf(" 0 ... Be silent\n");
+ printf(" 1 ... Output one line per test (and summary)\n");
+ printf(" 2 ... As 1 and failed conditions (this is default)\n");
+ printf(" 3 ... As 1 and all conditions (and extended summary)\n");
+ printf(" -q, --quiet Same as --verbose=0\n");
+ printf(" --color[=WHEN] Enable colorized output\n");
+ printf(" (WHEN is one of 'auto', 'always', 'never')\n");
+ printf(" --no-color Same as --color=never\n");
+ printf(" -h, --help Display this help and exit\n");
+
+ if(acutest_list_size_ < 16) {
+ printf("\n");
+ acutest_list_names_();
+ }
+}
+
+static const ACUTEST_CMDLINE_OPTION_ acutest_cmdline_options_[] = {
+ { 's', "skip", 's', 0 },
+ { 0, "exec", 'e', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 'E', "no-exec", 'E', 0 },
+#if defined ACUTEST_WIN_
+ { 't', "time", 't', 0 },
+ { 0, "timer", 't', 0 }, /* kept for compatibility */
+#elif defined ACUTEST_HAS_POSIX_TIMER_
+ { 't', "time", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 0, "timer", 't', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ }, /* kept for compatibility */
+#endif
+ { 0, "no-summary", 'S', 0 },
+ { 0, "tap", 'T', 0 },
+ { 'l', "list", 'l', 0 },
+ { 'v', "verbose", 'v', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 'q', "quiet", 'q', 0 },
+ { 0, "color", 'c', ACUTEST_CMDLINE_OPTFLAG_OPTIONALARG_ },
+ { 0, "no-color", 'C', 0 },
+ { 'h', "help", 'h', 0 },
+ { 0, "worker", 'w', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ }, /* internal */
+ { 'x', "xml-output", 'x', ACUTEST_CMDLINE_OPTFLAG_REQUIREDARG_ },
+ { 0, NULL, 0, 0 }
+};
+
+static int
+acutest_cmdline_callback_(int id, const char* arg)
+{
+ switch(id) {
+ case 's':
+ acutest_skip_mode_ = 1;
+ break;
+
+ case 'e':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ acutest_no_exec_ = 0;
+ } else if(strcmp(arg, "never") == 0) {
+ acutest_no_exec_ = 1;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --exec.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case 'E':
+ acutest_no_exec_ = 1;
+ break;
+
+ case 't':
+#if defined ACUTEST_WIN_ || defined ACUTEST_HAS_POSIX_TIMER_
+ if(arg == NULL || strcmp(arg, "real") == 0) {
+ acutest_timer_ = 1;
+ #ifndef ACUTEST_WIN_
+ } else if(strcmp(arg, "cpu") == 0) {
+ acutest_timer_ = 2;
+ #endif
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --time.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+#endif
+ break;
+
+ case 'S':
+ acutest_no_summary_ = 1;
+ break;
+
+ case 'T':
+ acutest_tap_ = 1;
+ break;
+
+ case 'l':
+ acutest_list_names_();
+ acutest_exit_(0);
+ break;
+
+ case 'v':
+ acutest_verbose_level_ = (arg != NULL ? atoi(arg) : acutest_verbose_level_+1);
+ break;
+
+ case 'q':
+ acutest_verbose_level_ = 0;
+ break;
+
+ case 'c':
+ if(arg == NULL || strcmp(arg, "always") == 0) {
+ acutest_colorize_ = 1;
+ } else if(strcmp(arg, "never") == 0) {
+ acutest_colorize_ = 0;
+ } else if(strcmp(arg, "auto") == 0) {
+ /*noop*/
+ } else {
+ fprintf(stderr, "%s: Unrecognized argument '%s' for option --color.\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case 'C':
+ acutest_colorize_ = 0;
+ break;
+
+ case 'h':
+ acutest_help_();
+ acutest_exit_(0);
+ break;
+
+ case 'w':
+ acutest_worker_ = 1;
+ acutest_worker_index_ = atoi(arg);
+ break;
+ case 'x':
+ acutest_xml_output_ = fopen(arg, "w");
+ if (!acutest_xml_output_) {
+ fprintf(stderr, "Unable to open '%s': %s\n", arg, strerror(errno));
+ acutest_exit_(2);
+ }
+ break;
+
+ case 0:
+ if(acutest_lookup_(arg) == 0) {
+ fprintf(stderr, "%s: Unrecognized unit test '%s'\n", acutest_argv0_, arg);
+ fprintf(stderr, "Try '%s --list' for list of unit tests.\n", acutest_argv0_);
+ acutest_exit_(2);
+ }
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_UNKNOWN_:
+ fprintf(stderr, "Unrecognized command line option '%s'.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_MISSINGARG_:
+ fprintf(stderr, "The command line option '%s' requires an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+
+ case ACUTEST_CMDLINE_OPTID_BOGUSARG_:
+ fprintf(stderr, "The command line option '%s' does not expect an argument.\n", arg);
+ fprintf(stderr, "Try '%s --help' for more information.\n", acutest_argv0_);
+ acutest_exit_(2);
+ break;
+ }
+
+ return 0;
+}
+
+
+#ifdef ACUTEST_LINUX_
+static int
+acutest_is_tracer_present_(void)
+{
+ /* Must be large enough so the line 'TracerPid: ${PID}' can fit in. */
+ static const int OVERLAP = 32;
+
+ char buf[512];
+ int tracer_present = 0;
+ int fd;
+ size_t n_read = 0;
+
+ fd = open("/proc/self/status", O_RDONLY);
+ if(fd == -1)
+ return 0;
+
+ while(1) {
+ static const char pattern[] = "TracerPid:";
+ const char* field;
+
+ while(n_read < sizeof(buf) - 1) {
+ ssize_t n;
+
+ n = read(fd, buf + n_read, sizeof(buf) - 1 - n_read);
+ if(n <= 0)
+ break;
+ n_read += n;
+ }
+ buf[n_read] = '\0';
+
+ field = strstr(buf, pattern);
+ if(field != NULL && field < buf + sizeof(buf) - OVERLAP) {
+ pid_t tracer_pid = (pid_t) atoi(field + sizeof(pattern) - 1);
+ tracer_present = (tracer_pid != 0);
+ break;
+ }
+
+ if(n_read == sizeof(buf) - 1) {
+ /* Move the tail with the potentially incomplete line we're looking
+ * for to the beginning of the buffer. */
+ memmove(buf, buf + sizeof(buf) - 1 - OVERLAP, OVERLAP);
+ n_read = OVERLAP;
+ } else {
+ break;
+ }
+ }
+
+ close(fd);
+ return tracer_present;
+}
+#endif
+
+#ifdef ACUTEST_MACOS_
+static bool
+acutest_AmIBeingDebugged(void)
+{
+ int junk;
+ int mib[4];
+ struct kinfo_proc info;
+ size_t size;
+
+ // Initialize the flags so that, if sysctl fails for some bizarre
+ // reason, we get a predictable result.
+ info.kp_proc.p_flag = 0;
+
+ // Initialize mib, which tells sysctl the info we want, in this case
+ // we're looking for information about a specific process ID.
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ // Call sysctl.
+ size = sizeof(info);
+ junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
+ assert(junk == 0);
+
+ // We're being debugged if the P_TRACED flag is set.
+ return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
+}
+#endif
+
+int
+main(int argc, char** argv)
+{
+ int i;
+
+ acutest_argv0_ = argv[0];
+
+#if defined ACUTEST_UNIX_
+ acutest_colorize_ = isatty(STDOUT_FILENO);
+#elif defined ACUTEST_WIN_
+ #if defined _BORLANDC_
+ acutest_colorize_ = isatty(_fileno(stdout));
+ #else
+ acutest_colorize_ = _isatty(_fileno(stdout));
+ #endif
+#else
+ acutest_colorize_ = 0;
+#endif
+
+ /* Count all test units */
+ acutest_list_size_ = 0;
+ for(i = 0; acutest_list_[i].func != NULL; i++)
+ acutest_list_size_++;
+
+ acutest_test_data_ = (struct acutest_test_data_*)calloc(acutest_list_size_, sizeof(struct acutest_test_data_));
+ if(acutest_test_data_ == NULL) {
+ fprintf(stderr, "Out of memory.\n");
+ acutest_exit_(2);
+ }
+
+ /* Parse options */
+ acutest_cmdline_read_(acutest_cmdline_options_, argc, argv, acutest_cmdline_callback_);
+
+ /* Initialize the proper timer. */
+ acutest_timer_init_();
+
+#if defined(ACUTEST_WIN_)
+ SetUnhandledExceptionFilter(acutest_seh_exception_filter_);
+#ifdef _MSC_VER
+ _set_abort_behavior(0, _WRITE_ABORT_MSG);
+#endif
+#endif
+
+ /* By default, we want to run all tests. */
+ if(acutest_count_ == 0) {
+ for(i = 0; acutest_list_[i].func != NULL; i++)
+ acutest_remember_(i);
+ }
+
+ /* Guess whether we want to run unit tests as child processes. */
+ if(acutest_no_exec_ < 0) {
+ acutest_no_exec_ = 0;
+
+ if(acutest_count_ <= 1) {
+ acutest_no_exec_ = 1;
+ } else {
+#ifdef ACUTEST_WIN_
+ if(IsDebuggerPresent())
+ acutest_no_exec_ = 1;
+#endif
+#ifdef ACUTEST_LINUX_
+ if(acutest_is_tracer_present_())
+ acutest_no_exec_ = 1;
+#endif
+#ifdef ACUTEST_MACOS_
+ if(acutest_AmIBeingDebugged())
+ acutest_no_exec_ = 1;
+#endif
+#ifdef RUNNING_ON_VALGRIND
+ /* RUNNING_ON_VALGRIND is provided by optionally included <valgrind.h> */
+ if(RUNNING_ON_VALGRIND)
+ acutest_no_exec_ = 1;
+#endif
+ }
+ }
+
+ if(acutest_tap_) {
+ /* TAP requires we know test result ("ok", "not ok") before we output
+ * anything about the test, and this gets problematic for larger verbose
+ * levels. */
+ if(acutest_verbose_level_ > 2)
+ acutest_verbose_level_ = 2;
+
+ /* TAP harness should provide some summary. */
+ acutest_no_summary_ = 1;
+
+ if(!acutest_worker_)
+ printf("1..%d\n", (int) acutest_count_);
+ }
+
+ int index = acutest_worker_index_;
+ for(i = 0; acutest_list_[i].func != NULL; i++) {
+ int run = (acutest_test_data_[i].flags & ACUTEST_FLAG_RUN_);
+ if (acutest_skip_mode_) /* Run all tests except those listed. */
+ run = !run;
+ if(run)
+ acutest_run_(&acutest_list_[i], index++, i);
+ }
+
+ /* Write a summary */
+ if(!acutest_no_summary_ && acutest_verbose_level_ >= 1) {
+ if(acutest_verbose_level_ >= 3) {
+ acutest_colored_printf_(ACUTEST_COLOR_DEFAULT_INTENSIVE_, "Summary:\n");
+
+ printf(" Count of all unit tests: %4d\n", (int) acutest_list_size_);
+ printf(" Count of run unit tests: %4d\n", acutest_stat_run_units_);
+ printf(" Count of failed unit tests: %4d\n", acutest_stat_failed_units_);
+ printf(" Count of skipped unit tests: %4d\n", (int) acutest_list_size_ - acutest_stat_run_units_);
+ }
+
+ if(acutest_stat_failed_units_ == 0) {
+ acutest_colored_printf_(ACUTEST_COLOR_GREEN_INTENSIVE_, "SUCCESS:");
+ printf(" All unit tests have passed.\n");
+ } else {
+ acutest_colored_printf_(ACUTEST_COLOR_RED_INTENSIVE_, "FAILED:");
+ printf(" %d of %d unit tests %s failed.\n",
+ acutest_stat_failed_units_, acutest_stat_run_units_,
+ (acutest_stat_failed_units_ == 1) ? "has" : "have");
+ }
+
+ if(acutest_verbose_level_ >= 3)
+ printf("\n");
+ }
+
+ if (acutest_xml_output_) {
+#if defined ACUTEST_UNIX_
+ char *suite_name = basename(argv[0]);
+#elif defined ACUTEST_WIN_
+ char suite_name[_MAX_FNAME];
+ _splitpath(argv[0], NULL, NULL, suite_name, NULL);
+#else
+ const char *suite_name = argv[0];
+#endif
+ fprintf(acutest_xml_output_, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ fprintf(acutest_xml_output_, "<testsuite name=\"%s\" tests=\"%d\" errors=\"%d\" failures=\"%d\" skip=\"%d\">\n",
+ suite_name, (int)acutest_list_size_, acutest_stat_failed_units_, acutest_stat_failed_units_,
+ (int)acutest_list_size_ - acutest_stat_run_units_);
+ for(i = 0; acutest_list_[i].func != NULL; i++) {
+ struct acutest_test_data_ *details = &acutest_test_data_[i];
+ fprintf(acutest_xml_output_, " <testcase name=\"%s\" time=\"%.2f\">\n", acutest_list_[i].name, details->duration);
+ if (details->flags & ACUTEST_FLAG_FAILURE_)
+ fprintf(acutest_xml_output_, " <failure />\n");
+ if (!(details->flags & ACUTEST_FLAG_FAILURE_) && !(details->flags & ACUTEST_FLAG_SUCCESS_))
+ fprintf(acutest_xml_output_, " <skipped />\n");
+ fprintf(acutest_xml_output_, " </testcase>\n");
+ }
+ fprintf(acutest_xml_output_, "</testsuite>\n");
+ fclose(acutest_xml_output_);
+ }
+
+ acutest_cleanup_();
+
+ return (acutest_stat_failed_units_ == 0) ? 0 : 1;
+}
+
+
+#endif /* #ifndef TEST_NO_MAIN */
+
+#ifdef _MSC_VER
+ #pragma warning(pop)
+#endif
+
+#ifdef __cplusplus
+ } /* extern "C" */
+#endif
+
+#endif /* #ifndef ACUTEST_H */
diff --git a/test_c/cell.c b/test_c/cell.c
new file mode 100644
index 0000000..fc1ac41
--- /dev/null
+++ b/test_c/cell.c
@@ -0,0 +1,239 @@
+#include "acutest.h"
+#include "../src_c/cell.h"
+
+
+void test_cell_number() {
+ lsp_cell_t c[4];
+
+ lsp_cell_set_number(c, 0);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(c[0] == 0x0000);
+ TEST_ASSERT(lsp_cell_get_number(c) == 0);
+
+ lsp_cell_set_number(c, 1);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(c[0] == 0x0001);
+ TEST_ASSERT(lsp_cell_get_number(c) == 1);
+
+ lsp_cell_set_number(c, 0x0fff);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(c[0] == 0x0fff);
+ TEST_ASSERT(lsp_cell_get_number(c) == 0x0fff);
+
+ lsp_cell_set_number(c, 0x1fff);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(c[0] == 0x2000);
+ TEST_ASSERT(c[1] == 0x1fff);
+ TEST_ASSERT(lsp_cell_get_number(c) == 0x1fff);
+
+ lsp_cell_set_number(c, 0xffff);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(c[0] == 0x2003);
+ TEST_ASSERT(c[1] == 0x3fff);
+ TEST_ASSERT(lsp_cell_get_number(c) == 0xffff);
+
+ lsp_cell_set_number(c, 0x7fffffff);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 3);
+ TEST_ASSERT(c[0] == 0x2007);
+ TEST_ASSERT(c[1] == 0x7fff);
+ TEST_ASSERT(c[2] == 0x3fff);
+ TEST_ASSERT(lsp_cell_get_number(c) == 0x7fffffff);
+
+ lsp_cell_set_number(c, -1);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(c[0] == 0x1fff);
+ TEST_ASSERT(lsp_cell_get_number(c) == -1);
+
+ lsp_cell_set_number(c, -0x1000);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(c[0] == 0x1000);
+ TEST_ASSERT(lsp_cell_get_number(c) == -0x1000);
+
+ lsp_cell_set_number(c, -0x2fff);
+ TEST_ASSERT(lsp_cell_is_number(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(c[0] == 0x3fff);
+ TEST_ASSERT(c[1] == 0x1001);
+ TEST_ASSERT(lsp_cell_get_number(c) == -0x2fff);
+
+ c[0] = 0x4000;
+ TEST_ASSERT(!lsp_cell_is_number(c));
+}
+
+
+void test_cell_pair() {
+ lsp_cell_t c[2];
+
+ lsp_cell_set_pair(c, 1, 2);
+ TEST_ASSERT(lsp_cell_is_pair(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(lsp_cell_get_pair_first(c) == 1);
+ TEST_ASSERT(lsp_cell_get_pair_second(c) == 2);
+
+ lsp_cell_set_pair(c, 0x3fff, 0x0000);
+ TEST_ASSERT(lsp_cell_is_pair(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(lsp_cell_get_pair_first(c) == 0x3fff);
+ TEST_ASSERT(lsp_cell_get_pair_second(c) == 0x0000);
+
+ lsp_cell_set_pair(c, 0x0000, 0x3fff);
+ TEST_ASSERT(lsp_cell_is_pair(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(lsp_cell_get_pair_first(c) == 0x0000);
+ TEST_ASSERT(lsp_cell_get_pair_second(c) == 0x3fff);
+}
+
+
+void test_cell_string() {
+ lsp_cell_t c[4];
+
+ lsp_cell_set_string(c, 0);
+ TEST_ASSERT(lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_string_len(c) == 0);
+
+ lsp_cell_set_string(c, 1);
+ TEST_ASSERT(lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(lsp_cell_get_string_len(c) == 1);
+ lsp_cell_set_string_data(c, 0, 0xff);
+ TEST_ASSERT(c[1] == 0x7f80);
+ TEST_ASSERT(lsp_cell_get_string_data(c, 0) == 0xff);
+
+ lsp_cell_set_string(c, 2);
+ TEST_ASSERT(lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 3);
+ TEST_ASSERT(lsp_cell_get_string_len(c) == 2);
+ lsp_cell_set_string_data(c, 0, 0x00);
+ lsp_cell_set_string_data(c, 1, 0xff);
+ TEST_ASSERT(c[1] == 0x7f);
+ TEST_ASSERT(c[2] == 0x4000);
+ TEST_ASSERT(lsp_cell_get_string_data(c, 0) == 0x00);
+ TEST_ASSERT(lsp_cell_get_string_data(c, 1) == 0xff);
+}
+
+
+void test_cell_symbol() {
+ lsp_cell_t c[4];
+
+ lsp_cell_set_symbol(c, 0);
+ TEST_ASSERT(lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_symbol_len(c) == 0);
+
+ lsp_cell_set_symbol(c, 1);
+ TEST_ASSERT(lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 2);
+ TEST_ASSERT(lsp_cell_get_symbol_len(c) == 1);
+ lsp_cell_set_symbol_name(c, 0, 0xff);
+ TEST_ASSERT(c[1] == 0x7f80);
+ TEST_ASSERT(lsp_cell_get_symbol_name(c, 0) == 0xff);
+
+ lsp_cell_set_symbol(c, 2);
+ TEST_ASSERT(lsp_cell_is_symbol(c));
+ TEST_ASSERT(lsp_cell_is_string_or_symbol(c));
+ TEST_ASSERT(!lsp_cell_is_string(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 3);
+ TEST_ASSERT(lsp_cell_get_symbol_len(c) == 2);
+ lsp_cell_set_symbol_name(c, 0, 0x00);
+ lsp_cell_set_symbol_name(c, 1, 0xff);
+ TEST_ASSERT(c[1] == 0x7f);
+ TEST_ASSERT(c[2] == 0x4000);
+ TEST_ASSERT(lsp_cell_get_symbol_name(c, 0) == 0x00);
+ TEST_ASSERT(lsp_cell_get_symbol_name(c, 1) == 0xff);
+}
+
+
+void test_cell_builtin_function() {
+ lsp_cell_t c[1];
+
+ lsp_cell_set_builtin_function(c, 0);
+ TEST_ASSERT(lsp_cell_is_builtin_function(c));
+ TEST_ASSERT(lsp_cell_is_builtin(c));
+ TEST_ASSERT(!lsp_cell_is_builtin_syntax(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_builtin_index(c) == 0);
+
+ lsp_cell_set_builtin_function(c, 123);
+ TEST_ASSERT(lsp_cell_is_builtin_function(c));
+ TEST_ASSERT(lsp_cell_is_builtin(c));
+ TEST_ASSERT(!lsp_cell_is_builtin_syntax(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_builtin_index(c) == 123);
+}
+
+
+void test_cell_builtin_syntax() {
+ lsp_cell_t c[1];
+
+ lsp_cell_set_builtin_syntax(c, 0);
+ TEST_ASSERT(lsp_cell_is_builtin_syntax(c));
+ TEST_ASSERT(lsp_cell_is_builtin(c));
+ TEST_ASSERT(!lsp_cell_is_builtin_function(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_builtin_index(c) == 0);
+
+ lsp_cell_set_builtin_syntax(c, 123);
+ TEST_ASSERT(lsp_cell_is_builtin_syntax(c));
+ TEST_ASSERT(lsp_cell_is_builtin(c));
+ TEST_ASSERT(!lsp_cell_is_builtin_function(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 1);
+ TEST_ASSERT(lsp_cell_get_builtin_index(c) == 123);
+}
+
+
+void test_cell_function() {
+ lsp_cell_t c[4];
+
+ lsp_cell_set_function(c, 1, 2, 3);
+ TEST_ASSERT(lsp_cell_is_function(c));
+ TEST_ASSERT(lsp_cell_is_function_or_syntax(c));
+ TEST_ASSERT(!lsp_cell_is_syntax(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 4);
+ TEST_ASSERT(lsp_cell_get_function_parent_ctx(c) == 1);
+ TEST_ASSERT(lsp_cell_get_function_args(c) == 2);
+ TEST_ASSERT(lsp_cell_get_function_body(c) == 3);
+}
+
+
+void test_cell_syntax() {
+ lsp_cell_t c[4];
+
+ lsp_cell_set_syntax(c, 1, 2, 3);
+ TEST_ASSERT(lsp_cell_is_syntax(c));
+ TEST_ASSERT(lsp_cell_is_function_or_syntax(c));
+ TEST_ASSERT(!lsp_cell_is_function(c));
+ TEST_ASSERT(lsp_cell_get_size(c) == 4);
+ TEST_ASSERT(lsp_cell_get_syntax_parent_ctx(c) == 1);
+ TEST_ASSERT(lsp_cell_get_syntax_args(c) == 2);
+ TEST_ASSERT(lsp_cell_get_syntax_body(c) == 3);
+}
+
+
+TEST_LIST = {{"cell_number", test_cell_number},
+ {"cell_pair", test_cell_pair},
+ {"cell_string", test_cell_string},
+ {"cell_symbol", test_cell_symbol},
+ {"cell_builtin_function", test_cell_builtin_function},
+ {"cell_builtin_syntax", test_cell_builtin_syntax},
+ {"cell_function", test_cell_function},
+ {"cell_syntax", test_cell_syntax},
+ {NULL, NULL}};
diff --git a/test_c/mem.c b/test_c/mem.c
new file mode 100644
index 0000000..930bfde
--- /dev/null
+++ b/test_c/mem.c
@@ -0,0 +1,38 @@
+#include "acutest.h"
+#include "../src_c/mem.h"
+
+#define MEM_SIZE 32
+
+
+lsp_int8_t data[sizeof(lsp_mem_t) + MEM_SIZE * sizeof(lsp_cell_t)];
+
+
+void test_mem_gc() {
+ lsp_mem_t *m = (void *)data;
+ lsp_mem_init(m, MEM_SIZE);
+
+ for (lsp_int32_t i = 0; i < 10000; ++i) {
+ lsp_addr_t addr;
+ TEST_ASSERT(lsp_mem_create_number(m, i, &addr) == LSP_SUCCESS);
+ lsp_mem_dec_ref(m, addr);
+ }
+}
+
+
+void test_mem_no_gc() {
+ lsp_int32_t i;
+ lsp_mem_t *m = (void *)data;
+ lsp_mem_init(m, MEM_SIZE);
+
+ for (i = 0; i < 10000; ++i) {
+ lsp_addr_t addr;
+ if (lsp_mem_create_number(m, i, &addr) != LSP_SUCCESS)
+ break;
+ }
+
+ TEST_ASSERT(i > 1 && i < MEM_SIZE);
+}
+
+
+TEST_LIST = {
+ {"mem_gc", test_mem_gc}, {"mem_no_gc", test_mem_no_gc}, {NULL, NULL}};