diff --git a/CSharpStrange_Kuznetsov/.gitignore b/CSharpStrange_Kuznetsov/.gitignore
new file mode 100644
index 00000000..7102a822
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/.gitignore
@@ -0,0 +1,6 @@
+_build
+_coverage
+/_esy
+/node_modules
+/esy.lock
+/.melange.eobjs
diff --git a/CSharpStrange_Kuznetsov/.ocamlformat b/CSharpStrange_Kuznetsov/.ocamlformat
new file mode 100644
index 00000000..435021b6
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/.ocamlformat
@@ -0,0 +1,2 @@
+profile=janestreet
+version=0.27.0
\ No newline at end of file
diff --git a/CSharpStrange_Kuznetsov/.zanuda b/CSharpStrange_Kuznetsov/.zanuda
new file mode 100644
index 00000000..e7a24505
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/.zanuda
@@ -0,0 +1 @@
+forward mutability_check ignore bin/REPL.ml
diff --git a/CSharpStrange_Kuznetsov/COPYING b/CSharpStrange_Kuznetsov/COPYING
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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:
+
+ Copyright (C)
+ 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
+.
+
+ 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
+.
diff --git a/CSharpStrange_Kuznetsov/COPYING.CC0 b/CSharpStrange_Kuznetsov/COPYING.CC0
new file mode 100644
index 00000000..0e259d42
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/COPYING.CC0
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/CSharpStrange_Kuznetsov/COPYING.LESSER b/CSharpStrange_Kuznetsov/COPYING.LESSER
new file mode 100644
index 00000000..0a041280
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/COPYING.LESSER
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser 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
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam b/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam
new file mode 100644
index 00000000..8ad4512b
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam
@@ -0,0 +1,41 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+version: "0.3"
+synopsis: "An interpreter for strange subset of C# language"
+description:
+ "An interpreter for subset of C# language (some stuff like async/await and LINQ will be added later)"
+maintainer: ["Dmitrii Kuznetsov dmitrvlkuznetsov@gmail.com"]
+authors: ["Dmitrii Kuznetsov dmitrvlkuznetsov@gmail.com"]
+license: "LGPL-3.0-or-later"
+homepage: "https://github.com/Kakadu/fp25"
+doc: "https://kakadu.github.io/fp25/docs/CSharpStrange_Kuznetsov"
+bug-reports: "https://github.com/Kakadu/fp25"
+depends: [
+ "dune" {>= "3.7"}
+ "angstrom"
+ "ppx_inline_test" {with-test}
+ "ppx_expect"
+ "ppx_deriving"
+ "bisect_ppx"
+ "odoc" {with-doc}
+ "ocamlformat" {build}
+ "base"
+ "qcheck"
+]
+build: [
+ ["dune" "subst"] {dev}
+ [
+ "dune"
+ "build"
+ "-p"
+ name
+ "-j"
+ jobs
+ "@install"
+ "@runtest" {with-test}
+ "@doc" {with-doc}
+ ]
+]
+depexts: [
+ [ "mono-complete"] {os-distribution = "ubuntu"}
+]
diff --git a/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam.template b/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam.template
new file mode 100644
index 00000000..0b3494bf
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/CSharpStrange_Kuznetsov.opam.template
@@ -0,0 +1,3 @@
+depexts: [
+ [ "mono-complete"] {os-distribution = "ubuntu"}
+]
diff --git a/CSharpStrange_Kuznetsov/Makefile b/CSharpStrange_Kuznetsov/Makefile
new file mode 100644
index 00000000..e234db4b
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/Makefile
@@ -0,0 +1,49 @@
+.PHONY: repl tests test fmt lint celan
+
+all:
+ dune build
+
+repl:
+ dune build ./REPL.exe && rlwrap _build/default/REPL.exe
+
+tests: test
+test:
+ dune runtest
+
+celan: clean
+clean:
+ @$(RM) -r _build _coverage
+
+fmt:
+ dune build @fmt --auto-promote
+
+lint:
+ dune build @lint --force
+
+release:
+ dune build --profile=release
+ dune runtest --profile=release
+
+install:
+ dune b @install --profile=release
+ dune install
+
+ODIG_SWITCHES = --odoc-theme=odig.gruvbox.light
+ODIG_SWITCHES += --no-tag-index
+ODIG_SWITCHES += --no-pkg-deps
+odig:
+ odig odoc $(ODIG_SWITCHES) Lambda
+
+TEST_COV_D = /tmp/cov
+COVERAGE_OPTS = --coverage-path $(TEST_COV_D) --expect lib/ --expect tests/
+
+.PHONY: test_coverage coverage
+test_coverage: coverage
+coverage:
+ $(RM) -r $(TEST_COV_D)
+ mkdir -p $(TEST_COV_D)
+ BISECT_FILE=$(TEST_COV_D)/language dune runtest --no-print-directory \
+ --instrument-with bisect_ppx --force
+ bisect-ppx-report html $(COVERAGE_OPTS)
+ bisect-ppx-report summary $(COVERAGE_OPTS)
+ @echo "Use 'xdg-open _coverage/index.html' to see coverage report"
diff --git a/CSharpStrange_Kuznetsov/bin/REPL.ml b/CSharpStrange_Kuznetsov/bin/REPL.ml
new file mode 100644
index 00000000..c98fc45a
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/bin/REPL.ml
@@ -0,0 +1,55 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open C_sharp_strange_lib
+open Ast
+open Parser
+open Common
+open Typecheck
+open Interpret
+open Stdio
+
+type opts =
+ { mutable dump_parse_tree : bool
+ ; mutable file_path : string option
+ ; mutable eval : bool
+ }
+
+let () =
+ let opts = { dump_parse_tree = false; file_path = None; eval = false } in
+ let _ =
+ Arg.parse
+ [ "-parseast", Arg.Unit (fun () -> opts.dump_parse_tree <- true), "\n"
+ ; ( "-filepath"
+ , Arg.String (fun file_path -> opts.file_path <- Some file_path)
+ , "Input code in file\n" )
+ ; "-eval", Arg.Unit (fun () -> opts.eval <- true), "Run interpreter\n"
+ ]
+ (fun _ ->
+ let () = Stdlib.Format.eprintf "Something got wrong\n" in
+ Stdlib.exit 1)
+ "\n"
+ in
+ let path =
+ match opts.file_path with
+ | None -> String.trim @@ In_channel.input_all stdin
+ | Some path -> String.trim @@ In_channel.read_all path
+ in
+ match apply_parser parse_prog path with
+ | Ok ast ->
+ let () = if opts.dump_parse_tree then print_endline (show_program ast) in
+ if opts.eval
+ then (
+ match ast with
+ | Program cls ->
+ (match typecheck_main cls with
+ | Some _, Ok _ ->
+ (match interpret_program ast with
+ | Ok (Some v) -> exit v
+ | Ok None -> printf "void\n"
+ | Error e -> printf "Interpretation error: %s" (show_error e))
+ | None, Ok _ -> printf "Interpretation error: Main method not found"
+ | _, Error e -> printf "Typecheck error: %s" (show_error e)))
+ | Error msg -> printf "Parser error: Failed to parse file: %s" msg
+;;
diff --git a/CSharpStrange_Kuznetsov/bin/REPL.mli b/CSharpStrange_Kuznetsov/bin/REPL.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/bin/REPL.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
diff --git a/CSharpStrange_Kuznetsov/bin/dune b/CSharpStrange_Kuznetsov/bin/dune
new file mode 100644
index 00000000..f1f0d767
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/bin/dune
@@ -0,0 +1,7 @@
+(executable
+ (name REPL)
+ (public_name REPL)
+ (modules REPL)
+ (libraries c_sharp_strange_lib stdio)
+ (instrumentation
+ (backend bisect_ppx)))
diff --git a/CSharpStrange_Kuznetsov/bin/factorial.cs b/CSharpStrange_Kuznetsov/bin/factorial.cs
new file mode 100644
index 00000000..d405f3df
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/bin/factorial.cs
@@ -0,0 +1,20 @@
+public class Program
+{
+ public static int Factorial(int n)
+ {
+ if (n == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return n * Factorial(n - 1);
+ }
+ }
+
+ public static int Main()
+ {
+ System.Console.WriteLine(Factorial (5));
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/CSharpStrange_Kuznetsov/bin/fib.cs b/CSharpStrange_Kuznetsov/bin/fib.cs
new file mode 100644
index 00000000..5497efa4
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/bin/fib.cs
@@ -0,0 +1,14 @@
+public class Program {
+ public static int Fibonacci(int n)
+ {
+ if (n <= 1)
+ {
+ return n;
+ }
+ return Fibonacci(n - 1) + Fibonacci (n - 2);
+ }
+ public static int Main() {
+ System.Console.WriteLine(Fibonacci (6 ));
+ return 0;
+ }
+ }
diff --git a/CSharpStrange_Kuznetsov/dune b/CSharpStrange_Kuznetsov/dune
new file mode 100644
index 00000000..98e54536
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/dune
@@ -0,0 +1,7 @@
+(env
+ (dev
+ (flags
+ (:standard -alert @deprecated -warn-error -A -w -3-9-32-34-58)))
+ (release
+ (flags
+ (:standard -alert @deprecated -warn-error +A -w +A-4-40-42-44-70))))
diff --git a/CSharpStrange_Kuznetsov/dune-project b/CSharpStrange_Kuznetsov/dune-project
new file mode 100644
index 00000000..14d4b70e
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/dune-project
@@ -0,0 +1,36 @@
+(lang dune 3.7)
+
+(generate_opam_files true)
+
+(cram enable)
+
+(license LGPL-3.0-or-later)
+
+(authors "Dmitrii Kuznetsov dmitrvlkuznetsov@gmail.com")
+
+(maintainers "Dmitrii Kuznetsov dmitrvlkuznetsov@gmail.com")
+
+(bug_reports "https://github.com/Kakadu/fp25")
+
+(homepage "https://github.com/Kakadu/fp25")
+
+(package
+ (name CSharpStrange_Kuznetsov)
+ (synopsis "An interpreter for strange subset of C# language")
+ (description
+ "An interpreter for subset of C# language (some stuff like async/await and LINQ will be added later)")
+ (documentation "https://kakadu.github.io/fp25/docs/CSharpStrange_Kuznetsov")
+ (version 0.3)
+ (depends
+ dune
+ angstrom
+ (ppx_inline_test :with-test)
+ ppx_expect
+ ppx_deriving
+ bisect_ppx
+ (odoc :with-doc)
+ (ocamlformat :build)
+ base
+ qcheck
+ ; After adding dependencies to 'dune' files add the same dependecies here too
+ ))
diff --git a/CSharpStrange_Kuznetsov/lib/ast.ml b/CSharpStrange_Kuznetsov/lib/ast.ml
new file mode 100644
index 00000000..5a6aedcc
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/ast.ml
@@ -0,0 +1,113 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+(** Values types *)
+type val_type =
+ | ValInt of int (** Int value *)
+ | ValChar of char (** Char value *)
+ | ValNull (** Null *)
+ | ValBool of bool (** Bool value *)
+ | ValString of string (** string value *)
+[@@deriving eq, show { with_path = false }]
+
+(** Identidicator *)
+type ident = Id of string [@@deriving eq, show { with_path = false }]
+
+(** Basic types declarations *)
+type base_type =
+ | TypeInt (** Declaration of int *)
+ | TypeChar (** Declaration of char *)
+ | TypeBool (** Declaration of bool *)
+ | TypeString (** Declaration of string *)
+[@@deriving eq, show { with_path = false }]
+
+(** Type delcaration *)
+type _type =
+ | TypeBase of base_type (** Declaration of basic type *)
+ | TypeVoid (** Declaration of void *)
+[@@deriving eq, show { with_path = false }]
+
+(** Variable *)
+type var_type = TypeVar of _type [@@deriving eq, show { with_path = false }]
+
+(** Modifiers *)
+type modifier =
+ | MPublic (** Public modifier, used for main() method only *)
+ | MStatic (** Static modifier, used for main() method only *)
+ | MAsync (** Async modifier *)
+[@@deriving eq, show { with_path = false }]
+
+type var_decl = Var of var_type * ident [@@deriving eq, show { with_path = false }]
+type params = Params of var_decl list [@@deriving eq, show { with_path = false }]
+
+(** Binary operations *)
+type bin_op =
+ | OpAdd (** Sum: a [+] b *)
+ | OpSub (** a [-] b *)
+ | OpMul (** a [*] b *)
+ | OpDiv (** a [/] b in integers *)
+ | OpMod (** a [%] b *)
+ | OpEqual (** a [==] b *)
+ | OpNonEqual (** a [!=] b *)
+ | OpLess (** a [<] b *)
+ | OpMore (** a [>] b *)
+ | OpLessEqual (** a [<=] b *)
+ | OpMoreEqual (** a [>=] b *)
+ | OpAnd (** a [&&] b *)
+ | OpOr (** a [||] b *)
+ | OpAssign (** a [=] b *)
+[@@deriving eq, show { with_path = false }]
+
+(** Unary operations *)
+type un_op =
+ | OpNeg (** [-] a *)
+ | OpNot (** [!] a *)
+[@@deriving eq, show { with_path = false }]
+
+(** From clauses *)
+type from_clause = FromClause of string * ident
+[@@deriving eq, show { with_path = false }]
+
+(** Language expressions *)
+type expr =
+ | EValue of val_type (** Some value *)
+ | EBinOp of bin_op * expr * expr (** Binary operation *)
+ | EUnOp of un_op * expr (** Unary operation *)
+ | EId of ident (** Identificator expression *)
+ | EArrayAccess of expr * expr (** Array access: a = arr[i] *)
+ | EFuncCall of expr * args (** Call of function: name(arguments) *)
+ | EAwait of expr (** [Await] expression *)
+[@@deriving eq, show { with_path = false }]
+
+and args = Args of expr list [@@deriving show { with_path = false }]
+
+(** Language statements *)
+type stmt =
+ | SFor of stmt option * expr option * expr option * stmt
+ (** For cycle: [for] (int i = 0, j = 3; i < 4; i++, j--) \{\} *)
+ | SIf of expr * stmt * stmt option
+ (** If condition: [if] (a) [then] \{ b \} ([else] \{ c \} ) *)
+ | SWhile of expr * stmt (** While cycle: [while] (a) \{ \} *)
+ | SReturn of expr option (** Return: [return] (a) *)
+ | SBlock of stmt list (** Block of statements: \{ a \}; could be empty: \{\} *)
+ | SBreak (** Cycle [break] *)
+ | SContinue (** Cycle [continue] *)
+ | SExpr of expr (** Another expression *)
+ | SDecl of var_decl * expr option (** Var declaration *)
+[@@deriving eq, show { with_path = false }]
+
+(** C Sharp class fields *)
+type field =
+ | VarField of modifier list * var_type * ident * expr option
+ (** Class field - always initialized *)
+ | Method of modifier list * _type * ident * params * stmt (** Class method *)
+[@@deriving eq, show { with_path = false }]
+
+(** C Sharp class *)
+type c_sharp_class =
+ | Class of modifier list * ident * field list (** Basic class (Program) name *)
+[@@deriving eq, show { with_path = false }]
+
+(** Program AST *)
+type program = Program of c_sharp_class [@@deriving eq, show { with_path = false }]
diff --git a/CSharpStrange_Kuznetsov/lib/common.ml b/CSharpStrange_Kuznetsov/lib/common.ml
new file mode 100644
index 00000000..ff6fdfdf
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/common.ml
@@ -0,0 +1,95 @@
+(** Copyright 20265, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+
+(** Type checking errors *)
+type tc_error =
+ | NotImplemented (** Feature not implemented in the type checker *)
+ | OccursCheck
+ (** Occurs check failed during type unification (typically for recursive types) *)
+ | AccessError
+ (** Invalid access to a member (e.g., accessing private member from outside) *)
+ | ImpossibleResult of string
+ (** Type checking encountered an impossible state with additional context *)
+ | TypeMismatch (** Expected type does not match actual type *)
+ | OtherError of string (** Other type checking error with description *)
+[@@deriving show { with_path = false }]
+
+(** Runtime interpretation errors *)
+type interpret_error =
+ | NotImplemented (** Feature not implemented in the interpreter *)
+ | NoVariable of string (** Variable not found in current scope *)
+ | AddressNotFound of int (** Memory address not found in store *)
+ | VarDeclared of string (** Variable already declared in current scope *)
+ | TypeMismatch (** Type mismatch during runtime operation *)
+ | ImpossibleResult of string
+ (** Interpreter encountered an impossible state with additional context *)
+ | OtherError of string (** Other runtime error with description *)
+[@@deriving show { with_path = false }]
+
+(** Union type for all possible errors *)
+type error =
+ | TCError of tc_error (** Type checking error *)
+ | IError of interpret_error (** Runtime interpretation error *)
+[@@deriving show { with_path = false }]
+
+module Id = struct
+ type t = ident
+
+ let compare = compare
+end
+
+module IdMap = Map.Make (Id)
+
+type adr = Adr of int [@@deriving show { with_path = false }]
+
+module Adr = struct
+ type t = adr
+
+ let compare = compare
+end
+
+module AdrMap = Map.Make (Adr)
+
+type tc_var_info =
+ { var_type : var_type
+ ; initialized : bool
+ }
+[@@deriving show { with_path = false }, eq]
+
+type field_info =
+ { field_modifiers : modifier list
+ ; field_type : var_type
+ ; field_name : ident
+ ; field_init : expr option
+ ; is_static : bool
+ }
+
+type method_info =
+ { method_modifiers : modifier list
+ ; method_return : _type
+ ; method_name : ident
+ ; method_params : params
+ ; method_body : stmt
+ ; is_static : bool
+ ; is_main : bool
+ }
+
+type obj_content =
+ | TCLocalVar of tc_var_info
+ | TCField of field_info
+ | TCMethod of method_info
+
+type context = TCClass of c_sharp_class
+
+module TypeCheck = struct
+ type global_env = context IdMap.t
+ type local_env = obj_content IdMap.t
+ type curr_class = ident
+ type class_with_main = ident
+
+ type state =
+ global_env * local_env * curr_class option * _type option * class_with_main option
+end
diff --git a/CSharpStrange_Kuznetsov/lib/common.mli b/CSharpStrange_Kuznetsov/lib/common.mli
new file mode 100644
index 00000000..62f0fe67
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/common.mli
@@ -0,0 +1,118 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+
+(* Type checker error types *)
+type tc_error =
+ | NotImplemented
+ | OccursCheck
+ | AccessError
+ | ImpossibleResult of string
+ | TypeMismatch
+ | OtherError of string
+
+val pp_tc_error : Format.formatter -> tc_error -> unit
+val show_tc_error : tc_error -> string
+
+(* Interpreter error types *)
+type interpret_error =
+ | NotImplemented
+ | NoVariable of string
+ | AddressNotFound of int
+ | VarDeclared of string
+ | TypeMismatch
+ | ImpossibleResult of string
+ | OtherError of string
+
+val pp_interpret_error : Format.formatter -> interpret_error -> unit
+val show_interpret_error : interpret_error -> string
+
+(* Combined error type *)
+type error =
+ | TCError of tc_error
+ | IError of interpret_error
+
+val pp_error : Format.formatter -> error -> unit
+val show_error : error -> string
+
+(* Identifier module *)
+module Id : sig
+ type t = ident
+
+ val compare : t -> t -> int
+end
+
+(* Map from identifiers *)
+module IdMap : sig
+ include Map.S with type key = ident
+end
+
+(* Address type *)
+type adr = Adr of int
+
+val pp_adr : Format.formatter -> adr -> unit
+val show_adr : adr -> string
+
+(* Address module *)
+module Adr : sig
+ type t = adr
+
+ val compare : t -> t -> int
+end
+
+(* Map from addresses *)
+module AdrMap : sig
+ include Map.S with type key = adr
+end
+
+(* Variable information for type checker *)
+type tc_var_info =
+ { var_type : var_type
+ ; initialized : bool (* Whether the variable has been initialized *)
+ }
+
+val pp_tc_var_info : Format.formatter -> tc_var_info -> unit
+val show_tc_var_info : tc_var_info -> string
+val equal_tc_var_info : tc_var_info -> tc_var_info -> bool
+
+(* Field information for type checker *)
+type field_info =
+ { field_modifiers : modifier list
+ ; field_type : var_type
+ ; field_name : ident
+ ; field_init : expr option
+ ; is_static : bool
+ }
+
+(* Method information for type checker *)
+type method_info =
+ { method_modifiers : modifier list
+ ; method_return : _type
+ ; method_name : ident
+ ; method_params : params
+ ; method_body : stmt
+ ; is_static : bool
+ ; is_main : bool (* Whether this is the Main method *)
+ }
+
+(* Type checker content types *)
+type obj_content =
+ | TCLocalVar of tc_var_info (* Local variable *)
+ | TCField of field_info (* Class field *)
+ | TCMethod of method_info (* Class method *)
+
+(* Global context for type checker *)
+type context = TCClass of c_sharp_class
+
+(* Type checker state module *)
+module TypeCheck : sig
+ type global_env = context IdMap.t
+ type local_env = obj_content IdMap.t
+ type curr_class = ident
+ type class_with_main = ident
+
+ type state =
+ global_env * local_env * curr_class option * _type option * class_with_main option
+end
diff --git a/CSharpStrange_Kuznetsov/lib/dune b/CSharpStrange_Kuznetsov/lib/dune
new file mode 100644
index 00000000..e8ad75aa
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/dune
@@ -0,0 +1,9 @@
+(library
+ (name c_sharp_strange_lib)
+ (public_name CSharpStrange_Kuznetsov.Lib)
+ (modules Ast Parser Prettyprinter Typecheck Monads Common Interpret)
+ (libraries angstrom base)
+ (preprocess
+ (pps ppx_deriving.show ppx_deriving.eq ppx_expect ppx_inline_test))
+ (instrumentation
+ (backend bisect_ppx)))
diff --git a/CSharpStrange_Kuznetsov/lib/interpret.ml b/CSharpStrange_Kuznetsov/lib/interpret.ml
new file mode 100644
index 00000000..c1c77496
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/interpret.ml
@@ -0,0 +1,648 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+open Parser
+open Common
+open Typecheck
+
+let ( let* ) = Result.bind
+let return x = Ok x
+
+type adr = Adr of int [@@deriving show { with_path = false }]
+
+module IdMap = Map.Make (struct
+ type t = ident
+
+ let compare = compare
+ end)
+
+module LocMap = Map.Make (Int)
+
+(** Runtime values during interpretation *)
+type value =
+ | VInt of int (** Integer value (corresponds to C# int) *)
+ | VBool of bool (** Boolean value (corresponds to C# bool) *)
+ | VChar of char (** Character value (corresponds to C# char) *)
+ | VString of string (** String value (corresponds to C# string) *)
+ | VNull (** Null reference (corresponds to C# null) *)
+ | VObject of adr (** Object reference stored at given address in memory *)
+[@@deriving show { with_path = false }]
+
+type func =
+ { params : ident list
+ ; body : stmt
+ }
+
+type location = int
+
+type rt_var_info =
+ { loc : location
+ ; initialized : bool
+ }
+
+type env = rt_var_info IdMap.t list
+type func_env = (ident * func) list
+
+type store =
+ { mem : value LocMap.t
+ ; next_loc : int
+ }
+
+type object_id = int
+type field_value = value
+
+type object_state =
+ { obj_id : object_id
+ ; fields : (ident * field_value) list
+ }
+
+type class_def =
+ { fields : (ident * _type * expr option * bool) list
+ ; methods : (ident * func) list
+ }
+
+type runtime =
+ { env : env
+ ; fenv : func_env
+ ; store : store
+ ; objects : object_state list
+ ; curr_object : object_id option
+ ; class_def : class_def option
+ ; static_fields : (ident * value) list
+ }
+
+type return_code = int
+
+(* Printers *)
+let pp_value fmt = function
+ | VInt i -> Format.fprintf fmt "%d" i
+ | VBool b -> Format.fprintf fmt "%b" b
+ | VChar c -> Format.fprintf fmt "'%c'" c
+ | VString s -> Format.fprintf fmt "\"%S\"" s
+ | VNull -> Format.fprintf fmt "null"
+ | VObject (Adr a) -> Format.fprintf fmt "object@%d" a
+;;
+
+let value_to_exit_code = function
+ | VInt i -> i
+ | _ -> 0
+;;
+
+type exec_result =
+ | Normal
+ | Return of value
+ | Break
+ | Continue
+
+let empty_runtime =
+ { env = [ IdMap.empty ]
+ ; fenv = []
+ ; store = { next_loc = 0; mem = LocMap.empty }
+ ; objects = []
+ ; curr_object = None
+ ; class_def = None
+ ; static_fields = []
+ }
+;;
+
+let string_of_ident (Id s) = s
+
+let rec lookup_env id = function
+ | [] -> Error (IError (NoVariable ("Variable not found: " ^ string_of_ident id)))
+ | scope :: rest ->
+ (match IdMap.find_opt id scope with
+ | Some var_info -> Ok var_info.loc
+ | None -> lookup_env id rest)
+;;
+
+let check_initialized id (env : env) =
+ let rec find_var = function
+ | [] -> Error (IError (NoVariable (string_of_ident id)))
+ | scope :: rest ->
+ (match IdMap.find_opt id scope with
+ | Some (var_info : rt_var_info) ->
+ if var_info.initialized
+ then Ok ()
+ else Error (IError (OtherError "Value is not initialized"))
+ | None -> find_var rest)
+ in
+ find_var env
+;;
+
+let mark_initialized id env =
+ let rec mark_in_scope = function
+ | [] -> []
+ | scope :: rest ->
+ (match IdMap.find_opt id scope with
+ | Some (var_info : rt_var_info) ->
+ let new_var_info = { var_info with initialized = true } in
+ IdMap.add id new_var_info scope :: rest
+ | None -> scope :: mark_in_scope rest)
+ in
+ mark_in_scope env
+;;
+
+let rec lookup_func_opt (id : ident) = function
+ | [] -> None
+ | (id1, v) :: _ when id1 = id -> Some v
+ | _ :: rest -> lookup_func_opt id rest
+;;
+
+let lookup_store l store =
+ match LocMap.find_opt l store.mem with
+ | Some v -> Ok v
+ | None -> Error (IError (AddressNotFound l))
+;;
+
+let update_store l v store = { store with mem = LocMap.add l v store.mem }
+
+let alloc v store =
+ let loc = store.next_loc in
+ let store = { mem = LocMap.add loc v store.mem; next_loc = loc + 1 } in
+ loc, store
+;;
+
+let lookup_store_r l rt = lookup_store l rt.store
+let update_store_r l v rt = { rt with store = update_store l v rt.store }
+
+let alloc_r v rt =
+ let loc, store2 = alloc v rt.store in
+ loc, { rt with store = store2 }
+;;
+
+let value_of_val_type = function
+ | ValInt i -> VInt i
+ | ValChar c -> VChar c
+ | ValBool b -> VBool b
+ | ValString s -> VString s
+ | ValNull -> VNull
+;;
+
+let ident_of_vardecl = function
+ | Var (_, id) -> id
+;;
+
+let add_var (id : ident) (loc : location) (env : env) =
+ match env with
+ | scope :: rest ->
+ let var_info = { loc; initialized = false } in
+ Ok (IdMap.add id var_info scope :: rest)
+ | [] -> Error (IError (VarDeclared (string_of_ident id)))
+;;
+
+let push_scope env = Ok (IdMap.empty :: env)
+
+let pop_scope = function
+ | _ :: rest -> Ok rest
+ | [] -> Error (IError (OtherError "Cannot pop scope"))
+;;
+
+let var_field_of_ast = function
+ | VarField (mods, TypeVar typ, id, init) ->
+ Some
+ ( id
+ , typ
+ , init
+ , List.exists
+ (function
+ | MStatic -> true
+ | _ -> false)
+ mods )
+ | Method _ -> None
+;;
+
+let method_of_ast = function
+ | Method (_, _, id, Params params, body) ->
+ let params_list = List.map (fun (Var (_, id)) -> id) params in
+ Some (id, { params = params_list; body })
+ | VarField _ -> None
+;;
+
+let class_of_ast (Class (_, _, fields)) =
+ let fields_list = List.filter_map var_field_of_ast fields in
+ let methods_list = List.filter_map method_of_ast fields in
+ { fields = fields_list; methods = methods_list }
+;;
+
+let find_field obj_id field_id rt =
+ match List.find_opt (fun o -> o.obj_id = obj_id) rt.objects with
+ | None -> Error (IError (OtherError "Object not found"))
+ | Some obj ->
+ (match List.find_opt (fun (id, _) -> id = field_id) obj.fields with
+ | Some (_, v) -> Ok v
+ | None -> Error (IError (OtherError "Field not found")))
+;;
+
+let update_field obj_id field_id new_value rt =
+ let rec update_obj_list = function
+ | [] -> []
+ | obj :: rest when obj.obj_id = obj_id ->
+ let new_fields =
+ List.map
+ (fun (id, v) -> if id = field_id then id, new_value else id, v)
+ obj.fields
+ in
+ { obj with fields = new_fields } :: rest
+ | obj :: rest -> obj :: update_obj_list rest
+ in
+ { rt with objects = update_obj_list rt.objects }
+;;
+
+let find_static_field field_id rt =
+ match List.find_opt (fun (id, _) -> id = field_id) rt.static_fields with
+ | Some (_, v) -> Ok v
+ | None -> Error (NoVariable (string_of_ident field_id))
+;;
+
+let update_static_field field_id new_value rt =
+ let rec update_static_list = function
+ | [] -> [ field_id, new_value ]
+ | (id, _) :: rest when id = field_id -> (id, new_value) :: rest
+ | (id, v) :: rest -> (id, v) :: update_static_list rest
+ in
+ { rt with static_fields = update_static_list rt.static_fields }
+;;
+
+let rec eval_expr (rt : runtime) = function
+ | EValue v -> return (value_of_val_type v, rt)
+ | EId id ->
+ (match lookup_env id rt.env with
+ | Ok loc ->
+ (match check_initialized id rt.env with
+ | Ok () ->
+ let* v = lookup_store_r loc rt in
+ return (v, rt)
+ | Error e -> Error e)
+ | Error _ ->
+ (match find_static_field id rt with
+ | Ok v -> return (v, rt)
+ | Error _ ->
+ (match rt.curr_object with
+ | None -> Error (IError (NoVariable (string_of_ident id)))
+ | Some obj_id ->
+ (match find_field obj_id id rt with
+ | Ok v -> return (v, rt)
+ | Error e -> Error e))))
+ | EBinOp (OpAssign, left, right) ->
+ let* v, rt1 = eval_expr rt right in
+ (match left with
+ | EId id ->
+ (match lookup_env id rt1.env with
+ | Ok loc ->
+ let new_env = mark_initialized id rt1.env in
+ let rt2 = { rt1 with env = new_env } in
+ let rt3 = update_store_r loc v rt2 in
+ return (v, rt3)
+ | Error _ ->
+ (match find_static_field id rt1 with
+ | Ok _ ->
+ let rt2 = update_static_field id v rt1 in
+ return (v, rt2)
+ | Error _ ->
+ (match rt1.curr_object with
+ | None ->
+ Error (IError (OtherError ("Cannot assign to " ^ string_of_ident id)))
+ | Some obj_id ->
+ let rt2 = update_field obj_id id v rt1 in
+ return (v, rt2))))
+ | _ -> Error (IError TypeMismatch))
+ | EBinOp (OpAnd, e1, e2) ->
+ let* v1, rt1 = eval_expr rt e1 in
+ (match v1 with
+ | VBool false -> return (VBool false, rt1)
+ | VBool true ->
+ let* v2, rt2 = eval_expr rt1 e2 in
+ (match v2 with
+ | VBool b -> return (VBool b, rt2)
+ | _ -> Error (IError TypeMismatch))
+ | _ -> Error (IError TypeMismatch))
+ | EBinOp (OpOr, e1, e2) ->
+ let* v1, rt1 = eval_expr rt e1 in
+ (match v1 with
+ | VBool true -> return (VBool true, rt1)
+ | VBool false ->
+ let* v2, rt2 = eval_expr rt1 e2 in
+ (match v2 with
+ | VBool b -> return (VBool b, rt2)
+ | _ -> Error (IError TypeMismatch))
+ | _ -> Error (IError TypeMismatch))
+ | EBinOp (op, e1, e2) ->
+ let* v1, rt1 = eval_expr rt e1 in
+ let* v2, rt2 = eval_expr rt1 e2 in
+ eval_binop op v1 v2 rt2
+ | EUnOp (OpNot, e) ->
+ let* v, rt1 = eval_expr rt e in
+ (match v with
+ | VBool b -> return (VBool (not b), rt1)
+ | _ -> Error (IError TypeMismatch))
+ | EUnOp (OpNeg, e) ->
+ let* v, rt1 = eval_expr rt e in
+ (match v with
+ | VInt i -> return (VInt (-i), rt1)
+ | _ -> Error (IError TypeMismatch))
+ | EFuncCall (fn_expr, Args args) ->
+ (match fn_expr with
+ | EId id ->
+ (match lookup_func_opt id rt.fenv with
+ | None ->
+ Error (IError (OtherError ("Function not found: " ^ string_of_ident id)))
+ | Some f ->
+ let rec eval_args rt = function
+ | [] -> return ([], rt)
+ | e :: rest ->
+ let* v, rt1 = eval_expr rt e in
+ let* vs, rt2 = eval_args rt1 rest in
+ return (v :: vs, rt2)
+ in
+ let* arg_vals, rt2 = eval_args rt args in
+ let* v, rt3 = call_function rt2 f id arg_vals in
+ return (v, rt3))
+ | _ -> Error (IError (OtherError "Invalid function call")))
+ | EArrayAccess _ -> Error (IError NotImplemented)
+ | EAwait _ -> Error (IError NotImplemented)
+
+and eval_binop op v1 v2 rt =
+ match op, v1, v2 with
+ | OpAdd, VInt a, VInt b -> return (VInt (a + b), rt)
+ | OpSub, VInt a, VInt b -> return (VInt (a - b), rt)
+ | OpMul, VInt a, VInt b -> return (VInt (a * b), rt)
+ | OpDiv, VInt a, VInt b when b <> 0 -> return (VInt (a / b), rt)
+ | OpDiv, VInt _, VInt 0 -> Error (IError (ImpossibleResult "Div by zero"))
+ | OpMod, VInt a, VInt b when b <> 0 -> return (VInt (a mod b), rt)
+ | OpMod, VInt _, VInt 0 -> Error (IError (ImpossibleResult "Mod by zero"))
+ | OpEqual, v1, v2 -> return (VBool (v1 = v2), rt)
+ | OpNonEqual, v1, v2 -> return (VBool (v1 <> v2), rt)
+ | OpLess, VInt a, VInt b -> return (VBool (a < b), rt)
+ | OpMore, VInt a, VInt b -> return (VBool (a > b), rt)
+ | OpLessEqual, VInt a, VInt b -> return (VBool (a <= b), rt)
+ | OpMoreEqual, VInt a, VInt b -> return (VBool (a >= b), rt)
+ | OpAnd, VBool a, VBool b -> return (VBool (a && b), rt)
+ | OpOr, VBool a, VBool b -> return (VBool (a || b), rt)
+ | _ -> Error (IError (ImpossibleResult "Should not completed typecheck"))
+
+and call_function (rt : runtime) f id args =
+ let caller_env = rt.env in
+ let caller_obj = rt.curr_object in
+ match id with
+ | Id "System.Console.WriteLine" ->
+ (* Return null *)
+ let writeline = function
+ | [] ->
+ let _ = Format.printf "\n" in
+ return (VNull, rt)
+ | [ VInt i ] ->
+ let _ = Format.printf "%d\n" i in
+ return (VNull, rt)
+ | [ VChar c ] ->
+ let _ = Format.printf "%c\n" c in
+ return (VNull, rt)
+ | [ VString s ] ->
+ let _ = Format.printf "%S\n" s in
+ return (VNull, rt)
+ | [ VNull ] ->
+ let _ = Format.printf "null\n" in
+ return (VNull, rt)
+ | [ VObject (Adr a) ] ->
+ let _ = Format.printf "object@%d\n" a in
+ return (VNull, rt)
+ | _ -> Error (IError TypeMismatch)
+ in
+ writeline args
+ | _ ->
+ let rec bind_params env params args rt =
+ match params, args with
+ | [], [] -> return ({ rt with env }, rt)
+ | p :: ps, v :: vs ->
+ let loc, rt1 = alloc_r v rt in
+ let var_info = { loc; initialized = true } in
+ let* env2 =
+ match env with
+ | scope :: rest -> Ok (IdMap.add p var_info scope :: rest)
+ | [] -> Error (IError (OtherError "Empty environment in bind_params"))
+ in
+ bind_params env2 ps vs rt1
+ | _ -> Error (IError (OtherError "Argument mismatch"))
+ in
+ let* rt_func, _ = bind_params [ IdMap.empty ] f.params args rt in
+ let rt_with_this = { rt_func with curr_object = caller_obj } in
+ let* rt2, flow = exec_stmt rt_with_this f.body in
+ let restored_rt = { rt2 with env = caller_env; curr_object = caller_obj } in
+ (match flow with
+ | Return v -> return (v, restored_rt)
+ | Normal -> return (VNull, restored_rt)
+ | Break | Continue -> Error (IError (OtherError "Break/continue outside loop")))
+
+and exec_stmt (rt : runtime) = function
+ | SExpr e ->
+ let* _, rt1 = eval_expr rt e in
+ return (rt1, Normal)
+ | SDecl (decl, init) ->
+ let id = ident_of_vardecl decl in
+ let* value, rt1 =
+ match init with
+ | None -> return (VNull, rt)
+ | Some e -> eval_expr rt e
+ in
+ let loc, rt2 = alloc_r value rt1 in
+ let* env3 = add_var id loc rt2.env in
+ let env4 =
+ match init with
+ | Some _ -> mark_initialized id env3
+ | None -> env3
+ in
+ let rt3 = { rt2 with env = env4 } in
+ return (rt3, Normal)
+ | SIf (cond, then_s, else_s) ->
+ let* v, rt1 = eval_expr rt cond in
+ (match v with
+ | VBool true -> exec_stmt rt1 then_s
+ | VBool false ->
+ (match else_s with
+ | None -> return (rt1, Normal)
+ | Some s -> exec_stmt rt1 s)
+ | _ -> Error (IError TypeMismatch))
+ | SWhile (cond, body) ->
+ let rec loop rt =
+ let* v, rt1 = eval_expr rt cond in
+ match v with
+ | VBool true ->
+ let* rt2, r = exec_stmt rt1 body in
+ (match r with
+ | Normal -> loop rt2
+ | Continue -> loop rt2
+ | Break -> return (rt2, Normal)
+ | Return v -> return (rt2, Return v))
+ | VBool false -> return (rt1, Normal)
+ | _ -> Error (IError TypeMismatch)
+ in
+ loop rt
+ | SBlock stmts ->
+ let* env1 = push_scope rt.env in
+ let rt1 = { rt with env = env1 } in
+ let* rt2, flow = exec_block rt1 stmts in
+ let* env3 = pop_scope rt2.env in
+ let rt3 = { rt2 with env = env3 } in
+ return (rt3, flow)
+ | SReturn None -> return (rt, Return VNull)
+ | SReturn (Some e) ->
+ let* v, rt1 = eval_expr rt e in
+ return (rt1, Return v)
+ | SBreak -> return (rt, Break)
+ | SContinue -> return (rt, Continue)
+ | SFor (init, cond, step, body) ->
+ let* env0 = push_scope rt.env in
+ let rt0 = { rt with env = env0 } in
+ let* rt1 =
+ match init with
+ | None -> return rt0
+ | Some s ->
+ let* rt1, r = exec_stmt rt0 s in
+ (match r with
+ | Normal -> return rt1
+ | _ -> Error (IError (OtherError "Invalid control flow in for init")))
+ in
+ let rec loop rt =
+ let* cond_val, rt1 =
+ match cond with
+ | None -> return (VBool true, rt)
+ | Some e -> eval_expr rt e
+ in
+ match cond_val with
+ | VBool false -> return (rt1, Normal)
+ | VBool true ->
+ let* rt2, r = exec_stmt rt1 body in
+ (match r with
+ | Return v -> return (rt2, Return v)
+ | Break -> return (rt2, Normal)
+ | Continue | Normal ->
+ let* rt3 =
+ match step with
+ | None -> return rt2
+ | Some e ->
+ let* _, rt = eval_expr rt2 e in
+ return rt
+ in
+ loop rt3)
+ | _ -> Error (IError TypeMismatch)
+ in
+ let* rt2, flow = loop rt1 in
+ let* env3 = pop_scope rt2.env in
+ let rt3 = { rt2 with env = env3 } in
+ return (rt3, flow)
+
+and exec_block rt = function
+ | [] -> return (rt, Normal)
+ | s :: rest ->
+ let* rt1, r = exec_stmt rt s in
+ (match r with
+ | Normal -> exec_block rt1 rest
+ | _ -> return (rt1, r))
+;;
+
+let rec init_static_fields rt fields acc =
+ match fields with
+ | [] -> Ok (rt, List.rev acc)
+ | (id, typ, init_opt) :: rest ->
+ let default_value =
+ match typ with
+ | TypeBase TypeInt -> VInt 0
+ | TypeBase TypeBool -> VBool false
+ | TypeBase TypeChar -> VChar '\x00'
+ | TypeBase TypeString -> VString ""
+ | TypeVoid -> VNull
+ in
+ let rt_with_field =
+ { rt with static_fields = (id, default_value) :: rt.static_fields }
+ in
+ let* value, rt1 =
+ match init_opt with
+ | Some init_expr -> eval_expr rt_with_field init_expr
+ | None -> return (default_value, rt_with_field)
+ in
+ let rt2 = update_static_field id value rt1 in
+ init_static_fields rt2 rest ((id, value) :: acc)
+;;
+
+let rec init_instance_fields rt fields acc =
+ match fields with
+ | [] -> Ok (rt, List.rev acc)
+ | (id, typ, init_opt) :: rest ->
+ let* value, rt1 =
+ match init_opt with
+ | Some init_expr -> eval_expr rt init_expr
+ | None ->
+ let default =
+ match typ with
+ | TypeBase TypeInt -> VInt 0
+ | TypeBase TypeBool -> VBool false
+ | TypeBase TypeChar -> VChar '\x00'
+ | TypeBase TypeString -> VString ""
+ | TypeVoid -> VNull
+ in
+ return (default, rt)
+ in
+ init_instance_fields rt1 rest ((id, value) :: acc)
+;;
+
+let init_program (Class (_, name, fields)) =
+ let class_def = class_of_ast (Class ([], name, fields)) in
+ let rt = { empty_runtime with class_def = Some class_def } in
+ let builtin_functions =
+ [ Id "System.Console.WriteLine", { params = [ Id "value" ]; body = SBlock [] } ]
+ in
+ let rt_with_builtins =
+ List.fold_left
+ (fun rt (id, func) -> { rt with fenv = (id, func) :: rt.fenv })
+ rt
+ builtin_functions
+ in
+ let rt_with_methods =
+ List.fold_left
+ (fun rt (id, func) -> { rt with fenv = (id, func) :: rt.fenv })
+ rt_with_builtins
+ class_def.methods
+ in
+ let static_fields =
+ List.filter (fun (_, _, _, is_static) -> is_static) class_def.fields
+ in
+ let instance_fields =
+ List.filter (fun (_, _, _, is_static) -> not is_static) class_def.fields
+ in
+ let strip_static (id, typ, init, _) = id, typ, init in
+ let static_field_infos = List.map strip_static static_fields in
+ let instance_field_infos = List.map strip_static instance_fields in
+ let* rt1, static_vals = init_static_fields rt_with_methods static_field_infos [] in
+ let rt2 = { rt1 with static_fields = static_vals } in
+ let* rt3, instance_vals = init_instance_fields rt2 instance_field_infos [] in
+ let obj_id = 0 in
+ let program_object = { obj_id; fields = instance_vals } in
+ let rt4 = { rt3 with objects = [ program_object ]; curr_object = Some obj_id } in
+ Ok (None, rt4)
+;;
+
+let interpret_program = function
+ | Program cls ->
+ (match init_program cls with
+ | Ok (_, rt) ->
+ (match rt.class_def with
+ | Some class_def ->
+ (match List.find_opt (fun (id, _) -> id = Id "Main") class_def.methods with
+ | Some (_, main_func) ->
+ let* v, _ = call_function rt main_func (Id "Main") [] in
+ Ok (Some (value_to_exit_code v))
+ | None -> Error (IError (OtherError "Main method not found")))
+ | None -> Error (IError (OtherError "No class definition")))
+ | Error e -> Error e)
+;;
+
+let interpret str =
+ match apply_parser Parser.parse_prog str with
+ | Error _ -> Error (IError (OtherError "Parsing error"))
+ | Ok (Program prog) ->
+ (match typecheck_main prog with
+ | _, Error e -> Error e
+ | Some _, Ok _ -> interpret_program (Program prog)
+ | None, Ok _ -> Error (TCError (OtherError "Main method not found")))
+;;
diff --git a/CSharpStrange_Kuznetsov/lib/interpret.mli b/CSharpStrange_Kuznetsov/lib/interpret.mli
new file mode 100644
index 00000000..fb852a1a
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/interpret.mli
@@ -0,0 +1,25 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+open Common
+
+type adr = Adr of int
+
+type value =
+ | VInt of int
+ | VBool of bool
+ | VChar of char
+ | VString of string
+ | VNull
+ | VObject of adr
+
+val pp_value : Format.formatter -> value -> unit
+val show_value : value -> string
+
+type return_code = int
+
+(* Main funtions *)
+val interpret_program : program -> (return_code option, error) result
+val interpret : string -> (return_code option, error) result
diff --git a/CSharpStrange_Kuznetsov/lib/monads.ml b/CSharpStrange_Kuznetsov/lib/monads.ml
new file mode 100644
index 00000000..0d2133ad
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/monads.ml
@@ -0,0 +1,160 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Common
+
+module STATEERROR = struct
+ type ('st, 'a) t = 'st -> 'st * ('a, error) Result.t
+
+ let return : 'a -> ('st, 'a) t = fun x st -> st, Result.Ok x
+ let fail e st = st, Result.Error e
+
+ let ( >>= ) : ('st, 'a) t -> ('a -> ('st, 'b) t) -> ('st, 'b) t =
+ fun x f st ->
+ let st, x = x st in
+ match x with
+ | Result.Ok x -> f x st
+ | Result.Error e -> fail e st
+ ;;
+
+ let ( let* ) = ( >>= )
+ let ( *> ) : ('st, 'a) t -> ('st, 'b) t -> ('st, 'b) t = fun x1 x2 -> x1 >>= fun _ -> x2
+
+ let ( <|> ) : ('st, 'a) t -> ('st, 'a) t -> ('st, 'a) t =
+ fun x1 x2 st ->
+ let st, x = x1 st in
+ match x with
+ | Result.Ok x -> return x st
+ | Result.Error _ -> x2 st
+ ;;
+
+ let ( >>| ) : ('st, 'a) t -> ('a -> 'b) -> ('st, 'b) t =
+ fun x f st ->
+ let st, x = x st in
+ match x with
+ | Result.Ok x -> return (f x) st
+ | Result.Error e -> fail e st
+ ;;
+
+ let lift2 : ('a -> 'b -> 'c) -> ('st, 'a) t -> ('st, 'b) t -> ('st, 'c) t =
+ fun f a b -> a >>= fun r_a -> b >>= fun r_b -> return @@ f r_a r_b
+ ;;
+
+ let lift3
+ : ('a -> 'b -> 'c -> 'd) -> ('st, 'a) t -> ('st, 'b) t -> ('st, 'c) t -> ('st, 'd) t
+ =
+ fun f a b c -> lift2 f a b >>= fun f -> c >>| f
+ ;;
+
+ let read : ('st, 'st) t = fun st -> return st st
+ let write : 'st -> ('st, unit) t = fun new_st _ -> new_st, Result.Ok ()
+
+ let map : ('a -> ('st, 'b) t) -> 'a list -> ('st, 'b list) t =
+ fun f list ->
+ let f acc el = acc >>= fun acc -> f el >>= fun el -> return (el :: acc) in
+ List.fold_left f (return []) list >>| List.rev
+ ;;
+
+ let iter : ('a -> ('st, unit) t) -> 'a list -> ('st, unit) t =
+ fun f list ->
+ let f acc elem = acc *> f elem *> return () in
+ List.fold_left f (return ()) list
+ ;;
+
+ (*('st, 'a) t -> 'st -> 'st * ('a, error) Result.t *)
+ let run f = f
+end
+
+module TYPECHECK = struct
+ open Ast
+ open Common.TypeCheck
+ include STATEERROR
+
+ type 'a t = (TypeCheck.state, 'a) STATEERROR.t
+
+ let return_with_fail = function
+ | Some x -> return x
+ | None -> fail (TCError OccursCheck)
+ ;;
+
+ let read_local : 'a IdMap.t t =
+ read
+ >>= function
+ | _, l, _, _, _ -> return l
+ ;;
+
+ let read_local_el id f = read_local >>= fun l -> IdMap.find_opt id l |> f
+ let read_local_el_opt id = read_local_el id return
+ let read_local_el id = read_local_el id return_with_fail
+
+ let write_local n_l =
+ read
+ >>= function
+ | g, _, n, m, main -> write (g, n_l, n, m, main)
+ ;;
+
+ let write_local_el el_id el_ctx =
+ read_local >>= fun l -> write_local (IdMap.add el_id el_ctx l)
+ ;;
+
+ let write_meth_type_opt t =
+ read
+ >>= function
+ | g, l, n, _, main -> write (g, l, n, t, main)
+ ;;
+
+ let write_meth_type t = write_meth_type_opt (Some t)
+
+ let read_global : 'a IdMap.t t =
+ read
+ >>= function
+ | g, _, _, _, _ -> return g
+ ;;
+
+ let read_global_el id f = read_global >>= fun g -> IdMap.find_opt id g |> f
+ let read_global_el_opt id = read_global_el id return
+ let read_global_el id = read_global_el id return_with_fail
+
+ let read_meth_type : _type option t =
+ read
+ >>= function
+ | _, _, _, m_t, _ -> return m_t
+ ;;
+
+ let read_main_class : class_with_main option t =
+ read
+ >>= function
+ | _, _, _, _, main -> return main
+ ;;
+
+ let write_main_class main =
+ read
+ >>= function
+ | g, l, n, t, _ -> write (g, l, n, t, main)
+ ;;
+
+ let write_global n_g =
+ read
+ >>= function
+ | _, l, n, m, main -> write (n_g, l, n, m, main)
+ ;;
+
+ let write_global_el el_id el_ctx =
+ read_global >>= fun g -> write_global (IdMap.add el_id el_ctx g)
+ ;;
+
+ let get_curr_class_name : curr_class t =
+ read
+ >>= function
+ | _, _, Some n, _, _ -> return n
+ | _ ->
+ fail (TCError (ImpossibleResult "Current class can be 'none' only before running"))
+ ;;
+
+ let write_curr_class_name n =
+ read
+ >>= function
+ | g, l, _, t, main -> write (g, l, Some n, t, main)
+ ;;
+end
diff --git a/CSharpStrange_Kuznetsov/lib/monads.mli b/CSharpStrange_Kuznetsov/lib/monads.mli
new file mode 100644
index 00000000..05af80cf
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/monads.mli
@@ -0,0 +1,69 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Common
+
+(* State + Error monad combinators *)
+module STATEERROR : sig
+ type ('st, 'a) t = 'st -> 'st * ('a, error) Result.t
+
+ val return : 'a -> ('st, 'a) t
+ val fail : error -> ('st, 'a) t
+ val ( >>= ) : ('st, 'a) t -> ('a -> ('st, 'b) t) -> ('st, 'b) t
+ val ( let* ) : ('st, 'a) t -> ('a -> ('st, 'b) t) -> ('st, 'b) t
+ val ( *> ) : ('st, 'a) t -> ('st, 'b) t -> ('st, 'b) t
+ val ( <|> ) : ('st, 'a) t -> ('st, 'a) t -> ('st, 'a) t
+ val ( >>| ) : ('st, 'a) t -> ('a -> 'b) -> ('st, 'b) t
+ val lift2 : ('a -> 'b -> 'c) -> ('st, 'a) t -> ('st, 'b) t -> ('st, 'c) t
+
+ val lift3
+ : ('a -> 'b -> 'c -> 'd)
+ -> ('st, 'a) t
+ -> ('st, 'b) t
+ -> ('st, 'c) t
+ -> ('st, 'd) t
+
+ val read : ('st, 'st) t
+ val write : 'st -> ('st, unit) t
+ val map : ('a -> ('st, 'b) t) -> 'a list -> ('st, 'b list) t
+ val iter : ('a -> ('st, unit) t) -> 'a list -> ('st, unit) t
+ val run : ('st, 'a) t -> 'st -> 'st * ('a, error) Result.t
+end
+
+(* Typechecker-specific monad operations *)
+module TYPECHECK : sig
+ open Ast
+
+ type 'a t = (TypeCheck.state, 'a) STATEERROR.t
+
+ val return : 'a -> 'a t
+ val fail : error -> 'a t
+ val ( >>= ) : 'a t -> ('a -> 'b t) -> 'b t
+ val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
+ val ( *> ) : 'a t -> 'b t -> 'b t
+ val ( <|> ) : 'a t -> 'a t -> 'a t
+ val ( >>| ) : 'a t -> ('a -> 'b) -> 'b t
+ val lift2 : ('a -> 'b -> 'c) -> 'a t -> 'b t -> 'c t
+ val lift3 : ('a -> 'b -> 'c -> 'd) -> 'a t -> 'b t -> 'c t -> 'd t
+ val read_local : Common.obj_content IdMap.t t
+ val read_local_el_opt : ident -> Common.obj_content option t
+ val read_local_el : ident -> Common.obj_content t
+ val write_local : Common.obj_content IdMap.t -> unit t
+ val write_local_el : ident -> Common.obj_content -> unit t
+ val write_meth_type_opt : _type option -> unit t
+ val write_meth_type : _type -> unit t
+ val read_global : Common.context IdMap.t t
+ val read_global_el_opt : ident -> Common.context option t
+ val read_global_el : ident -> Common.context t
+ val read_meth_type : _type option t
+ val read_main_class : ident option t
+ val write_main_class : ident option -> unit t
+ val write_global : Common.context IdMap.t -> unit t
+ val write_global_el : ident -> Common.context -> unit t
+ val get_curr_class_name : ident t
+ val write_curr_class_name : ident -> unit t
+ val map : ('a -> 'b t) -> 'a list -> 'b list t
+ val iter : ('a -> unit t) -> 'a list -> unit t
+ val run : 'a t -> TypeCheck.state -> TypeCheck.state * ('a, error) Result.t
+end
diff --git a/CSharpStrange_Kuznetsov/lib/parser.ml b/CSharpStrange_Kuznetsov/lib/parser.ml
new file mode 100644
index 00000000..f5f97860
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/parser.ml
@@ -0,0 +1,380 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+open Angstrom
+open Base
+
+(* Chain functions *)
+
+let chainl1 expr op =
+ let rec pars e1 = lift2 (fun op1 e2 -> op1 e1 e2) op expr >>= pars <|> return e1 in
+ expr >>= pars
+;;
+
+let chainr1 expr op =
+ fix (fun x -> lift2 (fun op1 -> op1) (lift2 (fun e1 op2 -> op2 e1) expr op) x <|> expr)
+;;
+
+(* Special functions *)
+let reserved =
+ [ "true"
+ ; "false"
+ ; "if"
+ ; "else"
+ ; "while"
+ ; "public"
+ ; "static"
+ ; "void"
+ ; "string"
+ ; "char"
+ ; "int"
+ ; "bool"
+ ; "for"
+ ; "null"
+ ; "new"
+ ; "return"
+ ; "break"
+ ; "continue"
+ ; "class"
+ ; "async"
+ ; "await"
+ ]
+;;
+
+let in_reserved t = List.mem reserved t ~equal:String.equal
+
+let is_space = function
+ | ' ' | '\t' | '\n' | '\r' -> true
+ | _ -> false
+;;
+
+let is_token_sym = function
+ | 'a' .. 'z' | '0' .. '9' | 'A' .. 'Z' | '.' | '_' -> true
+ | _ -> false
+;;
+
+let skip_spaces = skip_while is_space
+let parens p = skip_spaces *> char '(' *> p <* skip_spaces <* char ')'
+let braces p = skip_spaces *> char '{' *> p <* skip_spaces <* char '}'
+let brackets p = skip_spaces *> char '[' *> p <* skip_spaces <* char ']'
+let skip_semicolons = fix (fun f -> skip_spaces *> char ';' *> f <|> return ())
+let skip_semicolons1 = skip_spaces *> char ';' *> skip_semicolons
+
+(* Values *)
+
+let parse_int =
+ take_while1 Char.is_digit
+ >>= fun num -> return @@ ValInt (Int.of_string num) <|> fail "Not an int"
+;;
+
+let parse_char =
+ char '\'' *> any_char
+ <* char '\''
+ >>= (fun c -> return @@ ValChar c)
+ <|> fail "Not a char"
+;;
+
+let parse_bool =
+ choice
+ [ string "true" *> return (ValBool true); string "false" *> return (ValBool false) ]
+ <|> fail "Not a bool"
+;;
+
+let parse_val_string =
+ char '\"'
+ *> take_till (function
+ | '\"' -> true
+ | _ -> false)
+ <* char '\"'
+ >>= (fun s -> return @@ ValString s)
+ <|> fail "Not a string"
+;;
+
+let parse_null = string "null" *> return ValNull <|> fail "Not a null"
+
+(* Modifiers *)
+
+let parse_modifiers =
+ many
+ (choice
+ [ string "public" *> skip_spaces *> return MPublic
+ ; string "static" *> skip_spaces *> return MStatic
+ ; string "async" *> skip_spaces *> return MAsync
+ ])
+;;
+
+(* Type words *)
+let parse_type_word =
+ take_while is_token_sym
+ >>= function
+ | "int" -> return TypeInt
+ | "char" -> return TypeChar
+ | "bool" -> return TypeBool
+ | "string" -> return TypeString
+ | _ -> fail "Wrong type word"
+;;
+
+let parse_base_type = parse_type_word >>= fun tp -> return @@ TypeBase tp
+let val_to_expr p = skip_spaces *> p >>| fun x -> EValue x
+
+let parse_value =
+ choice
+ [ val_to_expr parse_bool
+ ; val_to_expr parse_char
+ ; val_to_expr parse_int
+ ; val_to_expr parse_null
+ ; val_to_expr parse_val_string
+ ]
+ <|> fail "Value error"
+;;
+
+let parse_id =
+ take_while1 is_token_sym
+ >>= fun str ->
+ if String.is_empty str || in_reserved str || Char.is_digit str.[0]
+ then fail "Not an identifier"
+ else return (Id str)
+;;
+
+(* Expressions *)
+
+(* Variables && functions *)
+let parse_var_type =
+ choice ?failure_msg:(Some "Incorrect type") [ parse_base_type ]
+ >>= fun x -> return (TypeVar x)
+;;
+
+let parse_var =
+ let parse_decl_id typ_ = skip_spaces *> parse_id >>| fun id -> Var (typ_, id) in
+ skip_spaces *> parse_var_type >>= parse_decl_id
+;;
+
+let parse_id_expr = skip_spaces *> (parse_id >>| fun x -> EId x) <* skip_spaces
+let parse_call_id = parse_id_expr
+let parse_args_list arg = parens @@ sep_by (skip_spaces *> char ',') arg
+
+let parse_call_args id (arg : expr t) =
+ parse_args_list arg >>= fun args -> return @@ EFuncCall (id, Args args)
+;;
+
+let parse_params =
+ parens (sep_by (skip_spaces *> char ',' <* skip_spaces) parse_var)
+ >>= fun exp -> return (Params exp)
+;;
+
+let parse_call_expr (arg : expr t) = parse_call_id >>= fun id -> parse_call_args id arg
+
+(* Operations *)
+let parse_op op typ = skip_spaces *> string op *> return typ
+
+(* Binary operations *)
+let parse_bin_op op typ = parse_op op typ >>| fun t a b -> EBinOp (t, a, b)
+let ( ^+^ ) = parse_bin_op "+" OpAdd
+let ( ^-^ ) = parse_bin_op "-" OpSub
+let ( ^*^ ) = parse_bin_op "*" OpMul
+let ( ^/^ ) = parse_bin_op "/" OpDiv
+let ( ^%^ ) = parse_bin_op "%" OpMod
+let ( ^==^ ) = parse_bin_op "==" OpEqual
+let ( ^!=^ ) = parse_bin_op "!=" OpNonEqual
+let ( ^<^ ) = parse_bin_op "<" OpLess
+let ( ^>^ ) = parse_bin_op ">" OpMore
+let ( ^<=^ ) = parse_bin_op "<=" OpLessEqual
+let ( ^>=^ ) = parse_bin_op ">=" OpMoreEqual
+let ( ^&&^ ) = parse_bin_op "&&" OpAnd
+let ( ^||^ ) = parse_bin_op "||" OpOr
+let ( ^=^ ) = parse_bin_op "=" OpAssign
+
+(* Unary operations *)
+let parse_un_op op typ = parse_op op typ >>| fun t a -> EUnOp (t, a)
+let ( ^!^ ) = parse_un_op "!" OpNot
+let ( ^!-^ ) = parse_un_op "-" OpNeg
+
+let parse_ops =
+ fix (fun expr ->
+ let lv1 = choice [ parens expr; parse_value; parse_call_expr expr; parse_id_expr ] in
+ let lv2 =
+ many (choice [ ( ^!^ ); ( ^!-^ ) ])
+ >>= fun ops ->
+ let appl op = op in
+ lv1 >>= fun e -> return (List.fold_right ops ~f:appl ~init:e)
+ in
+ let lv3 = chainl1 lv2 (choice [ ( ^*^ ); ( ^/^ ); ( ^%^ ) ]) in
+ let lv4 = chainl1 lv3 (choice [ ( ^+^ ); ( ^-^ ) ]) in
+ let lv5 = chainl1 lv4 (choice [ ( ^<=^ ); ( ^>=^ ); ( ^<^ ); ( ^>^ ) ]) in
+ let lv6 = chainl1 lv5 (choice [ ( ^==^ ); ( ^!=^ ) ]) in
+ let lv7 = chainl1 lv6 (choice [ ( ^&&^ ) ]) in
+ let lv8 = chainl1 lv7 (choice [ ( ^||^ ) ]) in
+ chainr1 lv8 (choice [ ( ^=^ ) ]))
+ <|> fail "Expr error"
+;;
+
+let parse_assign =
+ lift3 (fun id eq ex -> eq id ex) parse_id_expr ( ^=^ ) parse_ops <|> fail "Assign error"
+;;
+
+(* Statements *)
+
+let get_opt p = p >>| fun x -> Some x
+
+let parse_decl =
+ lift2
+ (fun dcl e -> SDecl (dcl, e))
+ parse_var
+ (option None (skip_spaces *> char '=' *> parse_ops >>| fun e -> Some e))
+;;
+
+let expr_to_stmt expr = expr >>| fun x -> SExpr x
+let parse_stmt_ops = expr_to_stmt @@ choice [ parse_assign; parse_call_expr parse_ops ]
+
+let parse_if_else f_if_body =
+ let parse_if_cond = string "if" *> skip_spaces *> parens parse_ops in
+ let parse_else_cond ifls body =
+ skip_spaces
+ *> (get_opt @@ (string "else" *> skip_spaces *> choice [ ifls; body ]) <|> return None)
+ in
+ fix (fun ifls ->
+ let parse_body = f_if_body <|> (parse_stmt_ops <* skip_semicolons1) in
+ let parse_else_body = parse_else_cond ifls parse_body in
+ lift3
+ (fun cond if_body else_body -> SIf (cond, if_body, else_body))
+ parse_if_cond
+ parse_body
+ parse_else_body)
+ <|> fail "If error"
+;;
+
+let parse_for body =
+ let expr_to_option_stmt expr = get_opt @@ expr_to_stmt expr in
+ let p_body = body <|> (parse_stmt_ops <* skip_semicolons1) in
+ let p_for_init =
+ option None (get_opt parse_decl <|> expr_to_option_stmt parse_assign)
+ in
+ let p_for_expr = option None (get_opt parse_ops) in
+ let p_for =
+ lift2
+ (fun (f_init_p, f_cond_p, f_iter_p) f_body ->
+ SFor (f_init_p, f_cond_p, f_iter_p, f_body))
+ (parens
+ @@ lift3
+ (fun init cond incr -> init, cond, incr)
+ (p_for_init <* skip_spaces <* char ';')
+ (p_for_expr <* skip_spaces <* char ';')
+ p_for_expr)
+ p_body
+ in
+ string "for" *> p_for <|> fail "For error"
+;;
+
+let parse_while body =
+ let p_body = body <|> skip_semicolons1 *> parse_stmt_ops in
+ let p_cond = parens parse_ops in
+ let p_while = string "while" *> skip_spaces *> p_cond in
+ lift2 (fun cond body -> SWhile (cond, body)) p_while p_body <|> fail "While error"
+;;
+
+let parse_return =
+ lift2
+ (fun _ expr -> SReturn expr)
+ (string "return")
+ (parse_ops >>= (fun ret -> return (Some ret)) <|> return None)
+ <|> fail "Return error"
+;;
+
+let parse_break = skip_spaces *> string "break" *> return SBreak <|> fail "Break error"
+
+let parse_continue =
+ skip_spaces *> string "continue" *> return SContinue <|> fail "Continue error"
+;;
+
+let parse_block =
+ fix (fun block ->
+ let sc p = p <* skip_semicolons1 in
+ let op_sc p = p <* skip_semicolons in
+ let body_step =
+ choice
+ ?failure_msg:(Some "Error in some block sentence")
+ [ sc parse_decl
+ ; sc parse_break
+ ; sc parse_continue
+ ; sc parse_return
+ ; sc parse_stmt_ops
+ ; op_sc @@ parse_if_else block
+ ; op_sc @@ parse_for block
+ ; op_sc @@ parse_while block
+ ]
+ in
+ braces (skip_semicolons *> many (skip_spaces *> body_step))
+ >>= fun stmt_lst -> return @@ SBlock stmt_lst)
+;;
+
+(* Program class functions *)
+let parse_field_sign =
+ let f_value = skip_spaces *> char '=' *> get_opt parse_ops in
+ lift4
+ (fun f_modif f_type f_id f_val -> f_modif, f_type, f_id, f_val)
+ (skip_spaces *> parse_modifiers)
+ (skip_spaces *> parse_var_type)
+ (skip_spaces *> parse_id)
+ (option None f_value)
+ <* skip_semicolons1
+;;
+
+let parse_method_type =
+ let parse_void = string "void" *> return TypeVoid in
+ choice ?failure_msg:(Some "Not a method type") [ parse_base_type; parse_void ]
+;;
+
+let parse_method_sign =
+ lift4
+ (fun m_modif m_type m_id m_params -> m_modif, m_type, m_id, m_params)
+ (skip_spaces *> parse_modifiers)
+ (skip_spaces *> parse_method_type)
+ (skip_spaces *> parse_id)
+ (skip_spaces *> parse_params)
+;;
+
+let parse_method_member =
+ lift2
+ (fun (mds, tp, id, ps) bd -> Method (mds, tp, id, ps, bd))
+ parse_method_sign
+ parse_block
+;;
+
+let parse_field_member =
+ parse_field_sign
+ >>| function
+ | mds, tp, id, Some ex -> VarField (mds, tp, id, Some (EBinOp (OpAssign, EId id, ex)))
+ | mds, tp, id, None -> VarField (mds, tp, id, None)
+;;
+
+let parse_class_members =
+ let member =
+ choice ?failure_msg:(Some "Method error") [ parse_method_member; parse_field_member ]
+ in
+ braces @@ sep_by skip_spaces member
+;;
+
+let parse_class =
+ let class_id =
+ skip_spaces *> string "class" *> skip_spaces *> parse_id <|> fail "Class sign error"
+ in
+ lift3
+ (fun cl_mdf cl_id cl_mbs -> Class (cl_mdf, cl_id, cl_mbs))
+ (skip_spaces *> parse_modifiers)
+ class_id
+ parse_class_members
+;;
+
+let parse_prog : program t = parse_class <* skip_spaces >>| fun prog -> Program prog
+
+(* Main functions *)
+
+let apply_parser parser = parse_string ~consume:Consume.All parser
+
+let parse_option p str =
+ match apply_parser p str with
+ | Ok x -> Some x
+ | Error _ -> None
+;;
diff --git a/CSharpStrange_Kuznetsov/lib/parser.mli b/CSharpStrange_Kuznetsov/lib/parser.mli
new file mode 100644
index 00000000..d3a862af
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/parser.mli
@@ -0,0 +1,168 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+(** Parser for C#-like language *)
+
+open Ast
+
+(** {1 Basic parsers} *)
+
+(** List of reserved keywords *)
+val reserved : string list
+
+(** Check if a string is a reserved keyword *)
+val in_reserved : string -> bool
+
+(** Check if character is a whitespace *)
+val is_space : char -> bool
+
+(** Check if character can be part of a token (letter, digit, underscore) *)
+val is_token_sym : char -> bool
+
+(** Parser that skips whitespace characters *)
+val skip_spaces : unit Angstrom.t
+
+(** [parens p] parses [p] enclosed in parentheses *)
+val parens : 'a Angstrom.t -> 'a Angstrom.t
+
+(** [braces p] parses [p] enclosed in curly braces *)
+val braces : 'a Angstrom.t -> 'a Angstrom.t
+
+(** [brackets p] parses [p] enclosed in square brackets *)
+val brackets : 'a Angstrom.t -> 'a Angstrom.t
+
+(** Skips zero or more semicolons *)
+val skip_semicolons : unit Angstrom.t
+
+(** Skips one or more semicolons *)
+val skip_semicolons1 : unit Angstrom.t
+
+(** {1 Value parsers} *)
+
+(** Parses integer literals *)
+val parse_int : val_type Angstrom.t
+
+(** Parses character literals (e.g., 'a') *)
+val parse_char : val_type Angstrom.t
+
+(** Parses boolean literals (true/false) *)
+val parse_bool : val_type Angstrom.t
+
+(** Parses string literals (e.g., "hello") *)
+val parse_val_string : val_type Angstrom.t
+
+(** Parses null literal *)
+val parse_null : val_type Angstrom.t
+
+(** Parses any value literal as expression *)
+val parse_value : expr Angstrom.t
+
+(** {1 Identifier parsers} *)
+
+(** Parses identifiers (must not be reserved words) *)
+val parse_id : ident Angstrom.t
+
+(** {1 Type parsers} *)
+
+(** Parses type keywords (int, char, bool, string) *)
+val parse_type_word : base_type Angstrom.t
+
+(** Parses base types (TypeInt, TypeChar, TypeBool, TypeString) *)
+val parse_base_type : _type Angstrom.t
+
+(** Parses variable types (TypeVar of base_type) *)
+val parse_var_type : var_type Angstrom.t
+
+(** Parses method return types (including void) *)
+val parse_method_type : _type Angstrom.t
+
+(** {1 Modifier parsers} *)
+
+(** Parses zero or more modifiers (public, static, async) *)
+val parse_modifiers : modifier list Angstrom.t
+
+(** {1 Expression parsers} *)
+
+(** Main expression parser with operator precedence *)
+val parse_ops : expr Angstrom.t
+
+(** Parses assignment expressions *)
+val parse_assign : expr Angstrom.t
+
+(** Parses identifier as expression *)
+val parse_id_expr : expr Angstrom.t
+
+(** [parse_call_expr arg] parses function calls with given argument parser *)
+val parse_call_expr : expr Angstrom.t -> expr Angstrom.t
+
+(** {1 Statement parsers} *)
+
+(** Parses variable declarations *)
+val parse_decl : stmt Angstrom.t
+
+(** Parses expression statements *)
+val parse_stmt_ops : stmt Angstrom.t
+
+(** [parse_if_else body] parses if-else statements with given body parser *)
+val parse_if_else : stmt Angstrom.t -> stmt Angstrom.t
+
+(** [parse_for body] parses for loops with given body parser *)
+val parse_for : stmt Angstrom.t -> stmt Angstrom.t
+
+(** [parse_while body] parses while loops with given body parser *)
+val parse_while : stmt Angstrom.t -> stmt Angstrom.t
+
+(** Parses return statements *)
+val parse_return : stmt Angstrom.t
+
+(** Parses break statements *)
+val parse_break : stmt Angstrom.t
+
+(** Parses continue statements *)
+val parse_continue : stmt Angstrom.t
+
+(** Parses block statements (enclosed in {}) *)
+val parse_block : stmt Angstrom.t
+
+(** {1 Class and program parsers} *)
+
+(** Parses variable declarations (type + identifier) *)
+val parse_var : var_decl Angstrom.t
+
+(** Parses field signatures (modifiers, type, identifier, optional initializer) *)
+val parse_field_sign : (modifier list * var_type * ident * expr option) Angstrom.t
+
+(** Parses method signatures (modifiers, return type, identifier, parameters) *)
+val parse_method_sign : (modifier list * _type * ident * params) Angstrom.t
+
+(** Parses complete method definitions *)
+val parse_method_member : field Angstrom.t
+
+(** Parses complete field definitions *)
+val parse_field_member : field Angstrom.t
+
+(** Parses class members (fields and methods) enclosed in braces *)
+val parse_class_members : field list Angstrom.t
+
+(** Parses complete class definitions *)
+val parse_class : c_sharp_class Angstrom.t
+
+(** Parses complete programs *)
+val parse_prog : program Angstrom.t
+
+(** {1 Utility functions} *)
+
+(** [apply_parser parser str] applies parser to string and returns result *)
+val apply_parser : 'a Angstrom.t -> string -> ('a, string) result
+
+(** [parse_option parser str] tries to parse and returns option *)
+val parse_option : 'a Angstrom.t -> string -> 'a option
+
+(** {1 Chain combinators} *)
+
+(** Left-associative chaining combinator *)
+val chainl1 : 'a Angstrom.t -> ('a -> 'a -> 'a) Angstrom.t -> 'a Angstrom.t
+
+(** Right-associative chaining combinator *)
+val chainr1 : 'a Angstrom.t -> ('a -> 'a -> 'a) Angstrom.t -> 'a Angstrom.t
diff --git a/CSharpStrange_Kuznetsov/lib/prettyprinter.ml b/CSharpStrange_Kuznetsov/lib/prettyprinter.ml
new file mode 100644
index 00000000..ec2b1c68
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/prettyprinter.ml
@@ -0,0 +1,218 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Format
+open Ast
+
+let pp_list : 'a. (formatter -> 'a -> unit) -> string -> formatter -> 'a list -> unit =
+ fun pp sep fmt lst ->
+ let rec aux fmt = function
+ | [] -> ()
+ | [ x ] -> pp fmt x
+ | x :: xs -> fprintf fmt "%a%s%a" pp x sep aux xs
+ in
+ aux fmt lst
+;;
+
+let pp_option : 'a. (formatter -> 'a -> unit) -> formatter -> 'a option -> unit =
+ fun pp fmt -> function
+ | None -> ()
+ | Some x -> pp fmt x
+;;
+
+let pp_ident fmt (Id s) = fprintf fmt "%s" s
+
+let pp_base_type fmt = function
+ | TypeInt -> fprintf fmt "int"
+ | TypeChar -> fprintf fmt "char"
+ | TypeBool -> fprintf fmt "bool"
+ | TypeString -> fprintf fmt "string"
+;;
+
+let pp_type fmt = function
+ | TypeBase bt -> pp_base_type fmt bt
+ | TypeVoid -> fprintf fmt "void"
+;;
+
+let pp_var_type fmt (TypeVar t) = pp_type fmt t
+
+let pp_modifier fmt = function
+ | MPublic -> fprintf fmt "public"
+ | MStatic -> fprintf fmt "static"
+ | MAsync -> fprintf fmt "async"
+;;
+
+let pp_var_decl fmt (Var (vt, id)) = fprintf fmt "%a %a" pp_var_type vt pp_ident id
+
+let pp_bin_op fmt = function
+ | OpAdd -> fprintf fmt "+"
+ | OpSub -> fprintf fmt "-"
+ | OpMul -> fprintf fmt "*"
+ | OpDiv -> fprintf fmt "/"
+ | OpMod -> fprintf fmt "%%"
+ | OpEqual -> fprintf fmt "=="
+ | OpNonEqual -> fprintf fmt "!="
+ | OpLess -> fprintf fmt "<"
+ | OpMore -> fprintf fmt ">"
+ | OpLessEqual -> fprintf fmt "<="
+ | OpMoreEqual -> fprintf fmt ">="
+ | OpAnd -> fprintf fmt "&&"
+ | OpOr -> fprintf fmt "||"
+ | OpAssign -> fprintf fmt "="
+;;
+
+let pp_un_op fmt = function
+ | OpNot -> fprintf fmt "!"
+ | OpNeg -> fprintf fmt "-"
+;;
+
+let pp_val_type fmt = function
+ | ValInt n -> fprintf fmt "%d" n
+ | ValChar c -> fprintf fmt "'%c'" c
+ | ValNull -> fprintf fmt "null"
+ | ValBool b -> fprintf fmt "%b" b
+ | ValString s -> fprintf fmt {|%S|} s
+;;
+
+let rec pp_expr fmt = function
+ | EValue v -> pp_val_type fmt v
+ | EBinOp (OpAssign, EId id, e) -> fprintf fmt "%a = %a" pp_ident id pp_expr e
+ | EBinOp (op, e1, e2) -> fprintf fmt "(%a %a %a)" pp_expr e1 pp_bin_op op pp_expr e2
+ | EUnOp (op, e) ->
+ (match e with
+ | EValue _ | EId _ -> fprintf fmt "%a%a" pp_un_op op pp_expr e
+ | _ -> fprintf fmt "(%a%a)" pp_un_op op pp_expr e)
+ | EId id -> pp_ident fmt id
+ | EArrayAccess (e1, e2) -> fprintf fmt "%a[%a]" pp_expr e1 pp_expr e2
+ | EFuncCall (e, Args args) -> fprintf fmt "%a(%a)" pp_expr e (pp_list pp_expr ", ") args
+ | EAwait e -> fprintf fmt "await %a" pp_expr e
+;;
+
+let rec pp_stmt fmt = function
+ | SFor (init, cond, incr, body) ->
+ let pp_init fmt = function
+ | None -> fprintf fmt ""
+ | Some stmt ->
+ (match stmt with
+ | SDecl (vd, e) ->
+ fprintf
+ fmt
+ "%a%a"
+ pp_var_decl
+ vd
+ (fun fmt -> function
+ | None -> ()
+ | Some expr -> fprintf fmt " = %a" pp_expr expr)
+ e
+ | SExpr e -> pp_expr fmt e
+ | _ -> pp_stmt fmt stmt)
+ in
+ fprintf
+ fmt
+ "@[for (%a%a%a) {@ %a@]@ }"
+ pp_init
+ init
+ (fun fmt -> function
+ | None -> fprintf fmt ";"
+ | Some e -> fprintf fmt "; %a" pp_expr e)
+ cond
+ (fun fmt -> function
+ | None -> fprintf fmt ";"
+ | Some e -> fprintf fmt "; %a" pp_expr e)
+ incr
+ pp_stmt
+ body
+ | SIf (cond, then_branch, else_branch) ->
+ (match else_branch with
+ | None -> fprintf fmt "@[if (%a) {@ %a@]@ }" pp_expr cond pp_stmt then_branch
+ | Some else_stmt ->
+ fprintf
+ fmt
+ "@[if (%a) {@ %a@]@ }@ @[else {@ %a@]@ }@ "
+ pp_expr
+ cond
+ pp_stmt
+ then_branch
+ pp_stmt
+ else_stmt)
+ | SWhile (cond, body) ->
+ fprintf fmt "@[while (%a) {@ %a@]@ }" pp_expr cond pp_stmt body
+ | SReturn e ->
+ fprintf
+ fmt
+ "return%a;"
+ (fun fmt -> function
+ | None -> ()
+ | Some expr -> fprintf fmt " %a" pp_expr expr)
+ e
+ | SBlock stmts -> pp_sblock fmt stmts
+ | SBreak -> fprintf fmt "break;"
+ | SContinue -> fprintf fmt "continue;"
+ | SExpr e -> fprintf fmt "%a;" pp_expr e
+ | SDecl (vd, e) ->
+ fprintf
+ fmt
+ "%a%a;"
+ pp_var_decl
+ vd
+ (fun fmt -> function
+ | None -> ()
+ | Some expr -> fprintf fmt " = %a" pp_expr expr)
+ e
+
+and pp_sblock fmt = function
+ | [] -> fprintf fmt ""
+ | stmts -> fprintf fmt "@[%a@]" (pp_list pp_stmt " ") stmts
+;;
+
+let pp_field fmt = function
+ | VarField (mods, t, id, e) ->
+ fprintf
+ fmt
+ "%a %a %a%a;"
+ (pp_list pp_modifier " ")
+ mods
+ pp_var_type
+ t
+ pp_ident
+ id
+ (fun fmt -> function
+ | None -> ()
+ | Some expr ->
+ let init_expr =
+ match expr with
+ | EBinOp (OpAssign, _, e) -> e
+ | _ -> expr
+ in
+ fprintf fmt " = %a" pp_expr init_expr)
+ e
+ | Method (mods, t, id, Params params, body) ->
+ fprintf
+ fmt
+ "@[%a %a %a(%a)@ @[{@ %a@]@ @[}@]@ "
+ (pp_list pp_modifier " ")
+ mods
+ pp_type
+ t
+ pp_ident
+ id
+ (pp_list pp_var_decl ", ")
+ params
+ pp_stmt
+ body
+;;
+
+let pp_c_sharp_class fmt (Class (mods, id, fields)) =
+ fprintf
+ fmt
+ "@[%a class %a@ @[{@ %a@]@ @[}@]"
+ (pp_list pp_modifier " ")
+ mods
+ pp_ident
+ id
+ (pp_list pp_field " ")
+ fields
+;;
+
+let pp_prog fmt (Program cls) = pp_c_sharp_class fmt cls
diff --git a/CSharpStrange_Kuznetsov/lib/prettyprinter.mli b/CSharpStrange_Kuznetsov/lib/prettyprinter.mli
new file mode 100644
index 00000000..e67024f7
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/prettyprinter.mli
@@ -0,0 +1,58 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Format
+
+(** [pp_list pp sep fmt lst] prints a list [lst], using [pp] to print
+ elements and [sep] as separator between them *)
+val pp_list : (formatter -> 'a -> unit) -> string -> formatter -> 'a list -> unit
+
+(** [pp_option pp fmt opt] prints an optional value [opt], using [pp]
+ to print the value if it exists *)
+val pp_option : (formatter -> 'a -> unit) -> formatter -> 'a option -> unit
+
+(** [pp_ident fmt id] prints an identifier *)
+val pp_ident : formatter -> Ast.ident -> unit
+
+(** [pp_base_type fmt bt] prints a base type (int, char, bool, string) *)
+val pp_base_type : formatter -> Ast.base_type -> unit
+
+(** [pp_type fmt t] prints a type (base type or void) *)
+val pp_type : formatter -> Ast._type -> unit
+
+(** [pp_var_type fmt vt] prints a variable type *)
+val pp_var_type : formatter -> Ast.var_type -> unit
+
+(** [pp_modifier fmt m] prints a modifier (public, static, async) *)
+val pp_modifier : formatter -> Ast.modifier -> unit
+
+(** [pp_var_decl fmt vd] prints a variable declaration *)
+val pp_var_decl : formatter -> Ast.var_decl -> unit
+
+(** [pp_bin_op fmt op] prints a binary operator *)
+val pp_bin_op : formatter -> Ast.bin_op -> unit
+
+(** [pp_un_op fmt op] prints a unary operator *)
+val pp_un_op : formatter -> Ast.un_op -> unit
+
+(** [pp_val_type fmt v] prints a literal value (number, character, null, bool, string) *)
+val pp_val_type : formatter -> Ast.val_type -> unit
+
+(** [pp_expr fmt e] prints an expression *)
+val pp_expr : formatter -> Ast.expr -> unit
+
+(** [pp_stmt fmt s] prints a statement *)
+val pp_stmt : formatter -> Ast.stmt -> unit
+
+(** [pp_sblock fmt stmts] prints a block of statements *)
+val pp_sblock : formatter -> Ast.stmt list -> unit
+
+(** [pp_field fmt f] prints a class field (variable or method) *)
+val pp_field : formatter -> Ast.field -> unit
+
+(** [pp_c_sharp_class fmt cls] prints a class definition *)
+val pp_c_sharp_class : formatter -> Ast.c_sharp_class -> unit
+
+(** [pp_prog fmt prog] prints a program (class) *)
+val pp_prog : formatter -> Ast.program -> unit
diff --git a/CSharpStrange_Kuznetsov/lib/typecheck.ml b/CSharpStrange_Kuznetsov/lib/typecheck.ml
new file mode 100644
index 00000000..1bce17e6
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/typecheck.ml
@@ -0,0 +1,438 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+open Monads.TYPECHECK
+open Common
+
+let value_to_type = function
+ | ValInt _ -> TypeBase TypeInt
+ | ValChar _ -> TypeBase TypeChar
+ | ValBool _ -> TypeBase TypeBool
+ | ValString _ -> TypeBase TypeString
+ | ValNull -> TypeBase TypeInt
+;;
+
+let string_of_ident (Id s) = s
+
+let vartype_to_type = function
+ | TypeVar t -> t
+;;
+
+let name_to_obj_ctx = read_local_el
+let eq f e1 e2 = if f e1 e2 then return e1 else fail (TCError TypeMismatch)
+let eq_type t1 t2 = eq equal__type t1 t2
+
+let field_of_ast = function
+ | VarField (mods, typ, id, init) ->
+ let is_static =
+ List.exists
+ (function
+ | MStatic -> true
+ | _ -> false)
+ mods
+ in
+ Ok
+ { field_modifiers = mods
+ ; field_type = typ
+ ; field_name = id
+ ; field_init = init
+ ; is_static
+ }
+ | Method _ -> Error (TCError TypeMismatch)
+;;
+
+(* Expected field, got method *)
+
+let method_of_ast = function
+ | Ast.Method (mods, ret_type, id, pms, body) ->
+ let is_static =
+ List.exists
+ (function
+ | MStatic -> true
+ | _ -> false)
+ mods
+ in
+ let is_main = equal_ident id (Id "Main") in
+ Ok
+ { method_modifiers = mods
+ ; method_return = ret_type
+ ; method_name = id
+ ; method_params = pms
+ ; method_body = body
+ ; is_static
+ ; is_main
+ }
+ | Ast.VarField _ -> Error (TCError TypeMismatch)
+;;
+
+(* Expected method, got field *)
+
+let get_class_memb id memb =
+ match memb with
+ | VarField (_, _, f_id, _) when equal_ident f_id id ->
+ (match field_of_ast memb with
+ | Ok f_info -> Some (TCField f_info)
+ | Error _ -> None)
+ | Method (_, _, m_id, _, _) when equal_ident m_id id ->
+ (match method_of_ast memb with
+ | Ok m_info -> Some (TCMethod m_info)
+ | Error _ -> None)
+ | _ -> None
+;;
+
+let builtin_methods =
+ [ ( Id "System.Console.WriteLine"
+ , { method_modifiers = [ MStatic ]
+ ; method_return = TypeVoid
+ ; method_name = Id "System.Console.WriteLine"
+ ; method_params = Params [ Var (TypeVar (TypeBase TypeInt), Id "value") ]
+ ; method_body = SBlock []
+ ; is_static = true
+ ; is_main = false
+ } )
+ ]
+;;
+
+let find_memb_from_obj obj_id id =
+ let find_memb b id f = List.find_map (f id) b in
+ let find_class_memb b id = find_memb b id get_class_memb in
+ read_global_el obj_id
+ >>= function
+ | TCClass (Class (_, _, b)) ->
+ (match find_class_memb b id with
+ | Some memb -> return (Some memb)
+ | None ->
+ read_global_el obj_id
+ >>= (function
+ | TCClass (Class (_, _, fields)) ->
+ let static_fields =
+ List.filter_map
+ (function
+ | VarField (mods, typ, fid, init)
+ when List.exists
+ (function
+ | MStatic -> true
+ | _ -> false)
+ mods
+ && equal_ident fid id ->
+ Some (field_of_ast (VarField (mods, typ, fid, init)))
+ | _ -> None)
+ fields
+ in
+ (match static_fields with
+ | [ Ok field_info ] -> return (Some (TCField field_info))
+ | _ ->
+ List.find_opt
+ (fun (builtin_id, _) -> equal_ident builtin_id id)
+ builtin_methods
+ |> Option.map (fun (_, info) -> TCMethod info)
+ |> return)))
+;;
+
+let find_memb_type = function
+ | TCLocalVar v -> return (vartype_to_type v.var_type)
+ | TCField f -> return (vartype_to_type f.field_type)
+ | TCMethod m -> return m.method_return
+;;
+
+let find_expr_type e expr_tc = expr_tc e >>= find_memb_type
+
+let tc_bin_op b e1 e2 expr_tc =
+ let compare_two_expr_type e1 e2 =
+ find_expr_type e1 expr_tc
+ >>= fun e1 -> find_expr_type e2 expr_tc >>= fun e2 -> eq_type e1 e2
+ in
+ let compare_three_expr_type e1 e2 t =
+ compare_two_expr_type e1 e2 >>= fun e -> eq_type e t
+ in
+ let return_rez rez =
+ let var_info = { var_type = TypeVar rez; initialized = true } in
+ return (TCLocalVar var_info)
+ in
+ match b with
+ | OpAdd | OpMul | OpSub | OpDiv | OpMod ->
+ compare_three_expr_type e1 e2 (TypeBase TypeInt) *> return_rez (TypeBase TypeInt)
+ | OpLess | OpLessEqual | OpMore | OpMoreEqual ->
+ compare_three_expr_type e1 e2 (TypeBase TypeInt) *> return_rez (TypeBase TypeBool)
+ | OpEqual | OpNonEqual -> compare_two_expr_type e1 e2 *> return_rez (TypeBase TypeBool)
+ | OpAnd | OpOr ->
+ compare_three_expr_type e1 e2 (TypeBase TypeBool) *> return_rez (TypeBase TypeBool)
+ | OpAssign ->
+ find_expr_type e1 expr_tc >>= fun e -> compare_two_expr_type e1 e2 *> return_rez e
+;;
+
+let tc_un_op u e expr_tc =
+ let tc_un_op u e =
+ find_expr_type e expr_tc
+ >>= fun t ->
+ match u with
+ | OpNot -> eq_type t (TypeBase TypeBool)
+ | OpNeg -> eq_type t (TypeBase TypeInt)
+ in
+ tc_un_op u e
+ >>= fun t ->
+ let var_info = { var_type = TypeVar t; initialized = true } in
+ return (TCLocalVar var_info)
+;;
+
+let tc_method_args (Params params) (Args args) expr_tc =
+ let params_to_list_of_type p =
+ List.map
+ (function
+ | Var (t, _) -> vartype_to_type t)
+ p
+ in
+ let args_to_list_of_type a = map (fun x -> expr_tc x >>= find_memb_type) a in
+ let compare_two_lists l1 l2 eq rez =
+ match List.compare_lengths l1 l2 with
+ | 0 ->
+ if List.equal eq l1 l2
+ then return rez
+ else fail (TCError (OtherError "Method invocation check error"))
+ | _ -> fail (TCError (OtherError "Method invocation check error"))
+ in
+ args_to_list_of_type args
+ >>= fun args ->
+ compare_two_lists (params_to_list_of_type params) args equal__type params
+;;
+
+let tc_method_invoke e args expr_tc =
+ expr_tc e
+ >>= function
+ | TCMethod m ->
+ tc_method_args m.method_params args expr_tc
+ >>= fun _ ->
+ (match m.method_return with
+ | TypeBase t ->
+ let var_info = { var_type = TypeVar (TypeBase t); initialized = true } in
+ return (TCLocalVar var_info)
+ | TypeVoid ->
+ fail (TCError (OtherError "Void methods cannot be used in expressions")))
+ | TCField _ -> fail (TCError (OtherError "Cannot call a field as a method"))
+ | TCLocalVar _ -> fail (TCError (OtherError "Cannot call a variable as a method"))
+;;
+
+let check_initialized n =
+ read_local_el n
+ >>= function
+ | TCLocalVar v when v.initialized -> return ()
+ | TCLocalVar _ -> fail (TCError (OtherError "Variable may be uninitialized"))
+ | TCField _ -> return ()
+ | TCMethod _ -> return ()
+;;
+
+let tc_expr =
+ let rec tc_expr_ = function
+ | EId n ->
+ name_to_obj_ctx n
+ >>= (fun ctx -> check_initialized n *> return ctx)
+ <|> (get_curr_class_name
+ >>= fun class_name ->
+ find_memb_from_obj class_name n
+ >>= function
+ | Some memb -> return memb
+ | None ->
+ fail (TCError (OtherError ("Variable not found: " ^ string_of_ident n))))
+ | EValue v ->
+ let var_info = { var_type = TypeVar (value_to_type v); initialized = true } in
+ return (TCLocalVar var_info)
+ | EFuncCall (e, args) -> tc_method_invoke e args tc_expr_
+ | EBinOp (b, e1, e2) -> tc_bin_op b e1 e2 tc_expr_
+ | EUnOp (u, e) -> tc_un_op u e tc_expr_
+ | _ -> fail (TCError NotImplemented)
+ in
+ tc_expr_
+;;
+
+let tc_expr_with_type e = tc_expr e >>= find_memb_type
+let eq_type_with_expr t e = tc_expr_with_type e >>= fun e_t -> eq_type e_t t
+
+let save_decl n ctx =
+ read_local_el_opt n
+ >>= function
+ | None -> write_local_el n ctx
+ | Some _ -> fail (TCError (OtherError "This variable is already declared"))
+;;
+
+let apply_local f = read_local >>= fun old_l -> f *> write_local old_l
+
+let rec tc_stmt =
+ let is_expr_bool e = tc_expr_with_type e >>= fun t -> eq_type t (TypeBase TypeBool) in
+ let tc_stmt_expr expr =
+ match expr with
+ | EFuncCall (e, args) ->
+ tc_expr e
+ >>= (function
+ | TCMethod { method_return = TypeVoid; method_params = pms; _ } ->
+ tc_method_args pms args tc_expr *> return ()
+ | TCMethod _ -> fail (TCError TypeMismatch)
+ | _ -> fail (TCError TypeMismatch))
+ | EBinOp (OpAssign, _, _) -> tc_expr expr *> return ()
+ | _ -> fail (TCError TypeMismatch)
+ in
+ let save_decl n t initialized =
+ read_local_el_opt n
+ >>= function
+ | None ->
+ let var_info = { var_type = TypeVar t; initialized } in
+ write_local_el n (TCLocalVar var_info)
+ | Some _ -> fail (TCError (OtherError "This variable is already declared"))
+ in
+ let tc_decl t n = function
+ | Some e -> eq_type_with_expr t e *> save_decl n t true *> return ()
+ | None -> save_decl n t false *> return ()
+ in
+ let tc_return e_opt =
+ read_meth_type
+ >>= fun m_t ->
+ match m_t, e_opt with
+ | Some TypeVoid, None -> return ()
+ | Some (TypeBase t), Some e ->
+ (eq_type_with_expr (TypeBase t) e
+ <|> fail (TCError (OtherError "Returned type does not match the function type")))
+ *> return ()
+ | _ -> fail (TCError TypeMismatch)
+ in
+ let opt_unpack f = function
+ | None -> return ()
+ | Some s -> f s *> return ()
+ in
+ let tc_for_state init cond iter =
+ let tc_init = function
+ | None -> return ()
+ | Some (SDecl (Var (TypeVar t, n), e)) -> tc_decl t n e
+ | _ -> fail (TCError TypeMismatch)
+ in
+ let tc_cond = opt_unpack is_expr_bool cond in
+ let tc_iter = opt_unpack tc_stmt_expr iter in
+ lift3 (fun _ _ _ -> ()) (tc_init init) tc_cond tc_iter
+ in
+ let tc_if_state cond b s_opt tc_st =
+ let tc_cond = is_expr_bool cond in
+ let tc_state = function
+ | Some st -> tc_st st
+ | None -> return ()
+ in
+ lift3 (fun _ _ _ -> ()) tc_cond (tc_st b) (tc_state s_opt)
+ in
+ function
+ | SExpr expr -> tc_stmt_expr expr
+ | SDecl (Var (TypeVar t, n), e) -> tc_decl t n e
+ | SReturn e -> tc_return e
+ | SWhile (e, s) -> apply_local (is_expr_bool e *> tc_stmt s)
+ | SFor (init, cond, iter, b) -> apply_local (tc_for_state init cond iter *> tc_stmt b)
+ | SIf (e, b, s_opt) -> apply_local (tc_if_state e b s_opt tc_stmt)
+ | SBlock st_l -> apply_local (iter tc_stmt st_l)
+ | SBreak | SContinue -> return () (* Will check execution in interpreter *)
+;;
+
+let tc_member mem class_fields =
+ let tc_class_field f_type = function
+ | Some e -> eq_type_with_expr (vartype_to_type f_type) e *> return ()
+ | None -> return ()
+ in
+ let save_params_to_l (Params params) =
+ let f = function
+ | Var (t, n) ->
+ let var_info = { var_type = t; initialized = true } in
+ write_local_el n (TCLocalVar var_info)
+ in
+ iter f params
+ in
+ let tc_meth typ params body class_fields =
+ apply_local
+ (let add_field_to_env = function
+ | VarField (mods, field_typ, id, init) ->
+ let is_static =
+ List.exists
+ (function
+ | MStatic -> true
+ | _ -> false)
+ mods
+ in
+ let field_info =
+ { field_modifiers = mods
+ ; field_type = field_typ
+ ; field_name = id
+ ; field_init = init
+ ; is_static
+ }
+ in
+ write_local_el id (TCField field_info)
+ | Method _ -> return ()
+ in
+ iter add_field_to_env class_fields
+ *> write_meth_type typ
+ *> save_params_to_l params
+ *> tc_stmt body)
+ in
+ let tc_class_method (mds, tp, id, pms, b) class_fields =
+ match method_of_ast (Method (mds, tp, id, pms, b)) with
+ | Ok m ->
+ if m.is_main
+ then (
+ let is_valid_signature =
+ mds = [ MPublic; MStatic ]
+ && pms = Params []
+ &&
+ match tp with
+ | TypeBase TypeInt | TypeVoid -> true
+ | _ -> false
+ in
+ if is_valid_signature
+ then
+ tc_meth tp (Params []) b class_fields *> read_main_class
+ >>= function
+ | None -> get_curr_class_name >>= fun n -> write_main_class (Some n)
+ | Some _ -> fail (TCError (OtherError "Main method already exists"))
+ else
+ fail
+ (TCError
+ (OtherError "Main must be static, non-async, no params, return int/void")))
+ else tc_meth tp pms b class_fields
+ | Error e -> fail e
+ in
+ match mem with
+ | VarField (_, tp, _, e_opt) -> tc_class_field tp e_opt
+ | Method (mds, tp, id, pms, b) -> tc_class_method (mds, tp, id, pms, b) class_fields
+;;
+
+let save_global id ctx =
+ read_global_el_opt id
+ >>= function
+ | None -> write_global_el id ctx
+ | Some _ -> fail (TCError (OtherError "This variable is already declared"))
+;;
+
+let tc_obj cl =
+ match cl with
+ | Class (_, id, fields) ->
+ let write_mems () =
+ let f mem =
+ match mem with
+ | VarField (_, _, id, _) ->
+ (match field_of_ast mem with
+ | Ok field_info -> save_decl id (TCField field_info)
+ | Error e -> fail e)
+ | Method (_, _, id, _, _) ->
+ (match method_of_ast mem with
+ | Ok method_info -> save_decl id (TCMethod method_info)
+ | Error e -> fail e)
+ in
+ iter f fields
+ in
+ let add_builtins =
+ iter (fun (id, method_info) -> save_decl id (TCMethod method_info)) builtin_methods
+ in
+ let tc_member_with_fields mem = tc_member mem fields in
+ let tc_mems = iter tc_member_with_fields fields in
+ let save_class = save_global id (TCClass cl) in
+ write_curr_class_name id
+ *> apply_local (write_mems () *> add_builtins *> save_class *> tc_mems)
+ *> return ()
+;;
+
+let typecheck prog = run (tc_obj prog) (IdMap.empty, IdMap.empty, None, None, None)
+let typecheck_main prog = typecheck prog |> fun ((_, _, _, _, main), res) -> main, res
diff --git a/CSharpStrange_Kuznetsov/lib/typecheck.mli b/CSharpStrange_Kuznetsov/lib/typecheck.mli
new file mode 100644
index 00000000..b118f770
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/lib/typecheck.mli
@@ -0,0 +1,9 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open Ast
+open Common
+
+val typecheck : c_sharp_class -> TypeCheck.state * (unit, error) result
+val typecheck_main : c_sharp_class -> Ast.ident option * (unit, Common.error) result
diff --git a/CSharpStrange_Kuznetsov/tests/dune b/CSharpStrange_Kuznetsov/tests/dune
new file mode 100644
index 00000000..9d397dd5
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/dune
@@ -0,0 +1,31 @@
+(library
+ (name tests)
+ (public_name CSharpStrange_Kuznetsov.Lib.Tests)
+ (modules Parser_tests Pp_tests Typecheck_tests Interpret_tests)
+ (libraries angstrom c_sharp_strange_lib)
+ (inline_tests)
+ (instrumentation
+ (backend bisect_ppx))
+ (preprocess
+ (pps ppx_expect)))
+
+(executable
+ (name qc_tests)
+ (modules Qc_tests)
+ (libraries c_sharp_strange_lib qcheck tests)
+ (preprocess
+ (pps ppx_expect))
+ (instrumentation
+ (backend bisect_ppx)))
+
+(cram
+ (applies_to fact)
+ (deps ../bin/REPL.exe ../bin/factorial.cs))
+
+(cram
+ (applies_to fib)
+ (deps ../bin/REPL.exe ../bin/fib.cs))
+
+(cram
+ (applies_to qc_test)
+ (deps qc_tests.exe))
diff --git a/CSharpStrange_Kuznetsov/tests/fact.t b/CSharpStrange_Kuznetsov/tests/fact.t
new file mode 100644
index 00000000..fd2a453f
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/fact.t
@@ -0,0 +1,40 @@
+ $ ../bin/REPL.exe -parseast -filepath="../bin/factorial.cs"
+ (Program
+ (Class ([MPublic], (Id "Program"),
+ [(Method ([MPublic; MStatic], (TypeBase TypeInt), (Id "Factorial"),
+ (Params [(Var ((TypeVar (TypeBase TypeInt)), (Id "n")))]),
+ (SBlock
+ [(SIf ((EBinOp (OpEqual, (EId (Id "n")), (EValue (ValInt 0)))),
+ (SBlock [(SReturn (Some (EValue (ValInt 1))))]),
+ (Some (SBlock
+ [(SReturn
+ (Some (EBinOp (OpMul, (EId (Id "n")),
+ (EFuncCall ((EId (Id "Factorial")),
+ (Args
+ [(EBinOp (OpSub, (EId (Id "n")),
+ (EValue (ValInt 1))))
+ ])
+ ))
+ ))))
+ ]))
+ ))
+ ])
+ ));
+ (Method ([MPublic; MStatic], (TypeBase TypeInt), (Id "Main"),
+ (Params []),
+ (SBlock
+ [(SExpr
+ (EFuncCall ((EId (Id "System.Console.WriteLine")),
+ (Args
+ [(EFuncCall ((EId (Id "Factorial")),
+ (Args [(EValue (ValInt 5))])))
+ ])
+ )));
+ (SReturn (Some (EValue (ValInt 0))))])
+ ))
+ ]
+ )))
+ $ mcs ../bin/factorial.cs && mono ../bin/factorial.exe <
+ (match x with
+ | Some _ -> ()
+ | None -> Format.print_string "void\n")
+ | Result.Error er -> Format.printf "%a\n%!" pp_error er
+;;
+
+let%expect_test "Main 1" =
+ test_interpret
+ {|
+ class Program {
+ static int b = 9;
+ static int c = 67;
+ static int a;
+ static bool r = false;
+ static string s = "ok";
+ static char h = 'a';
+ static bool t;
+
+ public static int Main() {
+ a = (50 % 2) + b - c;
+ r = s != "kkkk" && (190%22 == 100 * -2/5);
+ t = (a != b * c) || (a >= b) && (a == c +90);
+ System.Console.WriteLine(a);
+ return a;
+ }
+
+ } |};
+ [%expect
+ {|
+ -58 |}]
+;;
+
+let%expect_test "Main 2" =
+ test_interpret
+ {|
+ class Program {
+ static int n = 10;
+ public static int Main() {
+ int res = 0;
+ for(int i = 0; i < n; i = i+1) {
+ for(int j = 0; j < i; j = j+1) {
+ res = res + i *j;
+ }
+ }
+
+ System.Console.WriteLine(res);
+ return res;
+ }
+ } |};
+ [%expect
+ {|
+ 870 |}]
+;;
+
+let%expect_test "Main 3" =
+ test_interpret
+ {|
+ class Program {
+ static bool t;
+ static int a = 5;
+
+ public static int Main() {
+ int b = 5;
+ int c = 2;
+ t = true;
+ if (t) {
+ if (t && false) {
+ t = false;
+ return 1;
+ }
+ else if( a == b) {
+ a = c*67 + 7;
+ System.Console.WriteLine(a);
+ return a;
+ }
+ }
+ else {
+ return 3;
+ }
+ return 0;
+ }
+ } |};
+ [%expect
+ {|
+ 141 |}]
+;;
+
+let%expect_test "Main 4" =
+ test_interpret
+ {|
+ class Program {
+ static int x = 189;
+ static int s = 0;
+ public static int Main() {
+ while (x != 0) {
+ s = s + x % 10;
+ x = x/ 10;
+ }
+ System.Console.WriteLine(s);
+ return s;
+ }
+ } |};
+ [%expect
+ {|
+ 18 |}]
+;;
+
+let%expect_test "Functions 1" =
+ test_interpret
+ {|
+ class Program {
+ public static int is_right_triangle(int a, int b, int c) {
+ if ((a + b <= c) || (a + c <= b) || (b + c <= a)) {
+ return 0;
+ } else if ((a * a + b * b == c * c) || (a * a + c * c == b * b) || (b * b + c * c == a * a)) {
+ return 1;
+ } else {
+ return 2;
+ }
+ }
+ public static int Main() {
+ System.Console.WriteLine(is_right_triangle(3,4,5));
+ return;
+ }
+ } |};
+ [%expect
+ {|
+ (TCError TypeMismatch) |}]
+;;
+
+let%expect_test "Factorial with writeline" =
+ test_interpret
+ {|
+ class Program {
+ int Fac(int num) {
+ if (num == 1) {
+ return 1;
+ }
+ else
+ {
+ return num * Fac(num - 1);
+ }
+ }
+ public static int Main() {
+ int result = Fac(5);
+ System.Console.WriteLine(result);
+ return result;
+ }
+ } |};
+ [%expect
+ {|
+ 120 |}]
+;;
diff --git a/CSharpStrange_Kuznetsov/tests/interpret_tests.mli b/CSharpStrange_Kuznetsov/tests/interpret_tests.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/interpret_tests.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
diff --git a/CSharpStrange_Kuznetsov/tests/parser_tests.ml b/CSharpStrange_Kuznetsov/tests/parser_tests.ml
new file mode 100644
index 00000000..e9a912ee
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/parser_tests.ml
@@ -0,0 +1,472 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open C_sharp_strange_lib.Ast
+open C_sharp_strange_lib.Parser
+
+let%test "Parse one integer" = apply_parser parse_int {|1|} = Ok (ValInt 1)
+let%test "Parse one char" = apply_parser parse_char {|'c'|} = Ok (ValChar 'c')
+let%test "Parse true" = apply_parser parse_bool {|true|} = Ok (ValBool true)
+let%test "Parse false" = apply_parser parse_bool {|false|} = Ok (ValBool false)
+
+let%test "Parse string" =
+ apply_parser parse_val_string {|"sample"|} = Ok (ValString "sample")
+;;
+
+let%test "Parse parens" = apply_parser (parens parse_int) {|(1)|} = Ok (ValInt 1)
+let%test "Parse braces" = apply_parser (braces parse_int) {|{1}|} = Ok (ValInt 1)
+let%test "Parse brackets" = apply_parser (brackets parse_int) {|[1]|} = Ok (ValInt 1)
+let%test "Parse one modifier 1" = apply_parser parse_modifiers {|static|} = Ok [ MStatic ]
+let%test "Parse one modifier 2" = apply_parser parse_modifiers {|public|} = Ok [ MPublic ]
+
+let%test "Parse two modifiers" =
+ apply_parser parse_modifiers {|public async|} = Ok [ MPublic; MAsync ]
+;;
+
+let%test "Parse add 1" =
+ apply_parser parse_ops {| 1 + 2|}
+ = Ok (EBinOp (OpAdd, EValue (ValInt 1), EValue (ValInt 2)))
+;;
+
+let%test "Parse add 2" =
+ apply_parser parse_ops {| a + b|} = Ok (EBinOp (OpAdd, EId (Id "a"), EId (Id "b")))
+;;
+
+let%test "Parse many adds" =
+ apply_parser parse_ops {| 1 + 2 + 3|}
+ = Ok
+ (EBinOp
+ (OpAdd, EBinOp (OpAdd, EValue (ValInt 1), EValue (ValInt 2)), EValue (ValInt 3)))
+;;
+
+let%test "Parse adds with mul 1" =
+ apply_parser parse_ops {|1 + 2 * 3|}
+ = Ok
+ (EBinOp
+ (OpAdd, EValue (ValInt 1), EBinOp (OpMul, EValue (ValInt 2), EValue (ValInt 3))))
+;;
+
+let%test "Parse adds with mul 2" =
+ apply_parser parse_ops {| (1 + 2 ) * 3|}
+ = Ok
+ (EBinOp
+ (OpMul, EBinOp (OpAdd, EValue (ValInt 1), EValue (ValInt 2)), EValue (ValInt 3)))
+;;
+
+let%test "Parse div with mod" =
+ apply_parser parse_ops {| 1 / 2 % 3|}
+ = Ok
+ (EBinOp
+ (OpMod, EBinOp (OpDiv, EValue (ValInt 1), EValue (ValInt 2)), EValue (ValInt 3)))
+;;
+
+let%test "Parse div with mod" =
+ apply_parser parse_ops {| 1 - 2 / 3 + 4|}
+ = Ok
+ (EBinOp
+ ( OpAdd
+ , EBinOp
+ ( OpSub
+ , EValue (ValInt 1)
+ , EBinOp (OpDiv, EValue (ValInt 2), EValue (ValInt 3)) )
+ , EValue (ValInt 4) ))
+;;
+
+let%test "Parse simple boolean expression" =
+ apply_parser parse_ops {| ( 1 + 2 == 3 + 4 )|}
+ = Ok
+ (EBinOp
+ ( OpEqual
+ , EBinOp (OpAdd, EValue (ValInt 1), EValue (ValInt 2))
+ , EBinOp (OpAdd, EValue (ValInt 3), EValue (ValInt 4)) ))
+;;
+
+let%test "Parse complex boolean expression" =
+ apply_parser parse_ops {|( 1 + 2 < 3 + 4) && (5 == 8)|}
+ = Ok
+ (EBinOp
+ ( OpAnd
+ , EBinOp
+ ( OpLess
+ , EBinOp (OpAdd, EValue (ValInt 1), EValue (ValInt 2))
+ , EBinOp (OpAdd, EValue (ValInt 3), EValue (ValInt 4)) )
+ , EBinOp (OpEqual, EValue (ValInt 5), EValue (ValInt 8)) ))
+;;
+
+let%test "Parse ident expr" = apply_parser parse_ops {| x|} = Ok (EId (Id "x"))
+let%test "Parse id in expressions 1" = apply_parser parse_ops {| x|} = Ok (EId (Id "x"))
+
+let%test "Parse id in expressions 2" =
+ apply_parser parse_ops {|x + 1|} = Ok (EBinOp (OpAdd, EId (Id "x"), EValue (ValInt 1)))
+;;
+
+let%test "Parse var declaration 1" =
+ apply_parser parse_decl {|int x|}
+ = Ok (SDecl (Var (TypeVar (TypeBase TypeInt), Id "x"), None))
+;;
+
+let%test "Parse var declaration 2" =
+ apply_parser parse_decl {|int x = 1|}
+ = Ok (SDecl (Var (TypeVar (TypeBase TypeInt), Id "x"), Some (EValue (ValInt 1))))
+;;
+
+let%test "Parse multiple var declarations" =
+ apply_parser parse_decl {|int x = y = z = 1|}
+ = Ok
+ (SDecl
+ ( Var (TypeVar (TypeBase TypeInt), Id "x")
+ , Some
+ (EBinOp
+ ( OpAssign
+ , EId (Id "y")
+ , EBinOp (OpAssign, EId (Id "z"), EValue (ValInt 1)) )) ))
+;;
+
+let%test "Parse return 1" =
+ apply_parser parse_return {|return 5|} = Ok (SReturn (Some (EValue (ValInt 5))))
+;;
+
+let%test "Parse return 2" = apply_parser parse_return {|return|} = Ok (SReturn None)
+let%test "Parse break" = apply_parser parse_break {|break|} = Ok SBreak
+let%test "Parse continue" = apply_parser parse_continue {|continue|} = Ok SContinue
+let%test "Parse empty block 1" = apply_parser parse_block {|{}|} = Ok (SBlock [])
+let%test "Parse empty block 2" = apply_parser parse_block {|{;;;;}|} = Ok (SBlock [])
+
+let%test "Parse block 1" =
+ apply_parser parse_block {|{return 5;}|}
+ = Ok (SBlock [ SReturn (Some (EValue (ValInt 5))) ])
+;;
+
+let%test "Parse block 2" =
+ apply_parser parse_block {|{int x = 6; x = 6 + 1; return x;}|}
+ = Ok
+ (SBlock
+ [ SDecl (Var (TypeVar (TypeBase TypeInt), Id "x"), Some (EValue (ValInt 6)))
+ ; SExpr
+ (EBinOp
+ ( OpAssign
+ , EId (Id "x")
+ , EBinOp (OpAdd, EValue (ValInt 6), EValue (ValInt 1)) ))
+ ; SReturn (Some (EId (Id "x")))
+ ])
+;;
+
+let%test "Parse while" =
+ apply_parser
+ parse_block
+ {|
+ {
+ int x = 1;
+ while ( x < 1 )
+ {
+ x = 2;
+ break;
+ continue;
+ }
+ }|}
+ = Ok
+ (SBlock
+ [ SDecl (Var (TypeVar (TypeBase TypeInt), Id "x"), Some (EValue (ValInt 1)))
+ ; SWhile
+ ( EBinOp (OpLess, EId (Id "x"), EValue (ValInt 1))
+ , SBlock
+ [ SExpr (EBinOp (OpAssign, EId (Id "x"), EValue (ValInt 2)))
+ ; SBreak
+ ; SContinue
+ ] )
+ ])
+;;
+
+let%test "Parse for" =
+ apply_parser
+ parse_block
+ {|{
+ for (int i = 1;i < 5; i = i+1)
+ {
+ i = i + 1;
+ }
+ }|}
+ = Ok
+ (SBlock
+ [ SFor
+ ( Some
+ (SDecl
+ (Var (TypeVar (TypeBase TypeInt), Id "i"), Some (EValue (ValInt 1))))
+ , Some (EBinOp (OpLess, EId (Id "i"), EValue (ValInt 5)))
+ , Some
+ (EBinOp
+ ( OpAssign
+ , EId (Id "i")
+ , EBinOp (OpAdd, EId (Id "i"), EValue (ValInt 1)) ))
+ , SBlock
+ [ SExpr
+ (EBinOp
+ ( OpAssign
+ , EId (Id "i")
+ , EBinOp (OpAdd, EId (Id "i"), EValue (ValInt 1)) ))
+ ] )
+ ])
+;;
+
+let%test "Parse if" =
+ apply_parser
+ parse_block
+ {|{if (x == 5)
+ x=1;
+ else if (x == 2)
+ {
+ x=2;
+ }
+ }|}
+ = Ok
+ (SBlock
+ [ SIf
+ ( EBinOp (OpEqual, EId (Id "x"), EValue (ValInt 5))
+ , SExpr (EBinOp (OpAssign, EId (Id "x"), EValue (ValInt 1)))
+ , Some
+ (SIf
+ ( EBinOp (OpEqual, EId (Id "x"), EValue (ValInt 2))
+ , SBlock
+ [ SExpr (EBinOp (OpAssign, EId (Id "x"), EValue (ValInt 2))) ]
+ , None )) )
+ ])
+;;
+
+let%test "Parse field 1" =
+ apply_parser parse_field_member {|public int X;|}
+ = Ok (VarField ([ MPublic ], TypeVar (TypeBase TypeInt), Id "X", None))
+;;
+
+let%test "Parse field 2" =
+ apply_parser parse_field_member {|public int X = 1;|}
+ = Ok
+ (VarField
+ ( [ MPublic ]
+ , TypeVar (TypeBase TypeInt)
+ , Id "X"
+ , Some (EBinOp (OpAssign, EId (Id "X"), EValue (ValInt 1))) ))
+;;
+
+let%test "Parse method 1" =
+ apply_parser parse_method_member {|public int Func() {}|}
+ = Ok (Method ([ MPublic ], TypeBase TypeInt, Id "Func", Params [], SBlock []))
+;;
+
+let%test "Parse method 2" =
+ apply_parser
+ parse_method_member
+ {|public int Func()
+ {
+ return 2;
+ }|}
+ = Ok
+ (Method
+ ( [ MPublic ]
+ , TypeBase TypeInt
+ , Id "Func"
+ , Params []
+ , SBlock [ SReturn (Some (EValue (ValInt 2))) ] ))
+;;
+
+let%test "Parse method 3" =
+ apply_parser
+ parse_method_member
+ {|public int Factorial(int n)
+ {
+ if (n == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return n * Factorial(n - 1);
+ }
+ }|}
+ = Ok
+ (Method
+ ( [ MPublic ]
+ , TypeBase TypeInt
+ , Id "Factorial"
+ , Params [ Var (TypeVar (TypeBase TypeInt), Id "n") ]
+ , SBlock
+ [ SIf
+ ( EBinOp (OpEqual, EId (Id "n"), EValue (ValInt 0))
+ , SBlock [ SReturn (Some (EValue (ValInt 1))) ]
+ , Some
+ (SBlock
+ [ SReturn
+ (Some
+ (EBinOp
+ ( OpMul
+ , EId (Id "n")
+ , EFuncCall
+ ( EId (Id "Factorial")
+ , Args
+ [ EBinOp (OpSub, EId (Id "n"), EValue (ValInt 1))
+ ] ) )))
+ ]) )
+ ] ))
+;;
+
+let%test "Parse class 1" =
+ apply_parser
+ parse_class
+ {|
+ public class Sample {}|}
+ = Ok (Class ([ MPublic ], Id "Sample", []))
+;;
+
+let%test "Parse class 2" =
+ apply_parser
+ parse_class
+ {|
+ public class Sample {
+ public int X;
+ public int Y = 1;
+ }|}
+ = Ok
+ (Class
+ ( [ MPublic ]
+ , Id "Sample"
+ , [ VarField ([ MPublic ], TypeVar (TypeBase TypeInt), Id "X", None)
+ ; VarField
+ ( [ MPublic ]
+ , TypeVar (TypeBase TypeInt)
+ , Id "Y"
+ , Some (EBinOp (OpAssign, EId (Id "Y"), EValue (ValInt 1))) )
+ ] ))
+;;
+
+let%test "Parse class 3" =
+ apply_parser
+ parse_class
+ {|
+ public class Sample {
+
+ public int X;
+
+ public int add(int x) {
+ X = X + x;
+ }
+ }|}
+ = Ok
+ (Class
+ ( [ MPublic ]
+ , Id "Sample"
+ , [ VarField ([ MPublic ], TypeVar (TypeBase TypeInt), Id "X", None)
+ ; Method
+ ( [ MPublic ]
+ , TypeBase TypeInt
+ , Id "add"
+ , Params [ Var (TypeVar (TypeBase TypeInt), Id "x") ]
+ , SBlock
+ [ SExpr
+ (EBinOp
+ ( OpAssign
+ , EId (Id "X")
+ , EBinOp (OpAdd, EId (Id "X"), EId (Id "x")) ))
+ ] )
+ ] ))
+;;
+
+let%test "Parse factorial" =
+ apply_parser
+ parse_prog
+ {|
+ public class Program
+ {
+ public static void Main() {}
+
+ public int Factorial(int n)
+ {
+ if (n == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return n * Factorial(n - 1);
+ }
+ }
+ }
+
+ |}
+ = Ok
+ (Program
+ (Class
+ ( [ MPublic ]
+ , Id "Program"
+ , [ Method ([ MPublic; MStatic ], TypeVoid, Id "Main", Params [], SBlock [])
+ ; Method
+ ( [ MPublic ]
+ , TypeBase TypeInt
+ , Id "Factorial"
+ , Params [ Var (TypeVar (TypeBase TypeInt), Id "n") ]
+ , SBlock
+ [ SIf
+ ( EBinOp (OpEqual, EId (Id "n"), EValue (ValInt 0))
+ , SBlock [ SReturn (Some (EValue (ValInt 1))) ]
+ , Some
+ (SBlock
+ [ SReturn
+ (Some
+ (EBinOp
+ ( OpMul
+ , EId (Id "n")
+ , EFuncCall
+ ( EId (Id "Factorial")
+ , Args
+ [ EBinOp
+ ( OpSub
+ , EId (Id "n")
+ , EValue (ValInt 1) )
+ ] ) )))
+ ]) )
+ ] )
+ ] )))
+;;
+
+let%test "Short: Parse program with weird whitespace" =
+ let program =
+ {|
+ class Program
+ {
+
+
+ static int Main()
+ {
+
+ return 42 ;
+
+ }
+ }
+ |}
+ in
+ match apply_parser parse_prog program with
+ | Ok _ -> true
+ | Error _ -> false
+;;
+
+let%test "Short: Parse checking fields" =
+ let program =
+ {|
+ class Program {
+ int b = 9;
+ int c = b * 67;
+ int a = (50 % 2) + b - c;
+ bool r = (a != b * c) || (a >= b) && (a == c +90);
+ string s = "ok";
+ char h = 'a';
+
+ void M() {
+ a = 5;
+ r = s!= "kkkk" && (190%22 == 100 * -2/5) ;
+ }
+ }
+ |}
+ in
+ match apply_parser parse_prog program with
+ | Ok _ -> true
+ | Error _ -> false
+;;
diff --git a/CSharpStrange_Kuznetsov/tests/parser_tests.mli b/CSharpStrange_Kuznetsov/tests/parser_tests.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/parser_tests.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
diff --git a/CSharpStrange_Kuznetsov/tests/pp_tests.ml b/CSharpStrange_Kuznetsov/tests/pp_tests.ml
new file mode 100644
index 00000000..7d27f259
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/pp_tests.ml
@@ -0,0 +1,347 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open C_sharp_strange_lib
+open Prettyprinter
+open Parser
+open Format
+
+let test_pp _ source =
+ let prog = parse_option parse_prog source in
+ let pretty =
+ match prog with
+ | Some x -> asprintf "%a" pp_prog x
+ | None -> ""
+ in
+ let prog_after_pp = parse_option parse_prog pretty in
+ prog = prog_after_pp
+;;
+
+let samples =
+ [ ( "Factorial"
+ , {|
+public class Program
+{
+ public int Factorial(int n)
+ {
+ if (n == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return n * Factorial(n - 1);
+ }
+ }
+
+ public static void Main()
+ {
+ }
+}
+|}
+ )
+ ; ( "Cycles 1"
+ , {|
+public class Program
+{
+ public int Cycles(int n, bool e, string x)
+ {
+ int x = 0;
+ while (x < n)
+ {
+ if (x == -1)
+ {
+ break;
+ }
+
+ if (x == -2)
+ {
+ continue;
+ }
+
+ x = x + 1;
+ }
+
+ for (int i = 1; i < n; i++)
+ {
+ break;
+ }
+
+ for (;;)
+ {
+ break;
+ }
+
+ for (int i = 1;; i++)
+ {
+ break;
+ }
+ }
+
+ public static void Main()
+ {
+ Cycles(5, true, "sample");
+ }
+}
+|}
+ )
+ ; ( "Binops 1"
+ , {|
+public class Program
+{
+ public int Binops(int n, bool e, string x)
+ {
+ int x_ = n;
+ bool sample = !e || ((1 + 2 < 3 + 4) && (5 == 8));
+ string e = x;
+ char eeAe065ef = 'a';
+ e = null;
+ const int a = 1;
+ }
+
+ public static void Main()
+ {
+ Binops(5, true, "");
+ }
+}
+|}
+ )
+ ; ( "StaticClass"
+ , {|
+public static class Program {
+ static int result = 0;
+
+ public static void Main() {
+ int a = 5;
+ int b = 3;
+ result = a + b * 2;
+
+ if (result > 10) {
+ result = result - 10;
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "EmptyClass"
+ , {|
+public static class Program {
+ public static void Main() {
+ {
+ {
+
+ }
+ }
+ }
+}
+|}
+ )
+ ; ( "MultipleFields"
+ , {|
+public class Test {
+ int a, b, c;
+ static string x, y;
+ const int MAX = 100;
+}
+|}
+ )
+ ; ( "Simple arithmetic"
+ , {|
+public static class Program {
+ static int result = 0;
+
+ public static void Main() {
+ int a = 5;
+ int b = 3;
+ result = a + b * 2;
+
+ if (result > 10) {
+ result = result - 10;
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Cycles 2"
+ , {|
+public static class Program {
+ static int sum = 0;
+
+ public static void Main() {
+ int i = 0;
+
+ while (i < 5) {
+ sum = sum + i;
+ i = i + 1;
+ }
+
+ for (int j = 0; j < 3; j = j + 1) {
+ sum = sum + j;
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Boolean"
+ , {|
+public static class Program {
+ static bool flag = true;
+ static int value = 42;
+
+ public static void Main() {
+ bool condition = flag && (value > 40);
+
+ if (condition) {
+ value = 100;
+ } else {
+ value = 0;
+ }
+
+ if (value == 100) {
+ flag = false;
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Strings & chars"
+ , {|
+public static class Program {
+ static string message = "Hello";
+ static char symbol = 'A';
+
+ public static void Main() {
+ string name = "World";
+ string result = message + " " + name;
+
+ char nextSymbol = symbol + 1;
+
+ if (result != "Hello World") {
+ result = "Error";
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Cycles 3"
+ , {|
+public static class Program {
+ static int counter = 0;
+
+ public static void Main() {
+ for (int i = 0; i < 10; i = i + 1) {
+ if (i == 3) {
+ continue;
+ }
+
+ counter = counter + 1;
+
+ if (counter > 5) {
+ break;
+ }
+
+ {
+ int temp = counter * 2;
+ counter = temp;
+ }
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Complex exprs"
+ , {|
+public static class Program {
+ static int x = 10;
+ static int y = 20;
+ static bool ok = true;
+
+ public static void Main() {
+ int result = (x + y) * (x - y) / 2;
+
+ bool check = (x > y) && ok || (x <= y);
+
+ if (!check && result != 0) {
+ result = -result;
+ }
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Multiple definitions"
+ , {|
+public static class Program {
+ static int a = 1;
+ static int b = 2;
+ static string s1 = "first";
+ static string s2 = "second";
+ static bool b1 = true;
+ static bool b2 = false;
+ static char c1 = 'X';
+ static char c2 = 'Y';
+
+ public static void Main() {
+ int x = a + b;
+ string text = s1 + s2;
+ bool flag = b1 && b2;
+ char letter = c1;
+
+ return;
+ }
+}
+|}
+ )
+ ; ( "Binops 2"
+ , {|
+public static class Program {
+ static int value = 100;
+
+ public static void Main() {
+ int a = 5;
+ int b = 3;
+
+ int sum = a + b;
+ int sub = a - b;
+ int mul = a * b;
+ int div = a / b;
+ int mod = a % b;
+
+ bool eq = a == b;
+ bool neq = a != b;
+ bool lt = a < b;
+ bool gt = a > b;
+ bool lte = a <= b;
+ bool gte = a >= b;
+
+ bool and = true && false;
+ bool or = true || false;
+ bool not = !true;
+
+ int neg = -a;
+
+ return;
+ }
+}
+|}
+ )
+ ]
+;;
+
+let%test "All pp roundtrip tests" =
+ List.for_all (fun (name, source) -> test_pp name source) samples
+;;
diff --git a/CSharpStrange_Kuznetsov/tests/pp_tests.mli b/CSharpStrange_Kuznetsov/tests/pp_tests.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/pp_tests.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
diff --git a/CSharpStrange_Kuznetsov/tests/qc_test.t b/CSharpStrange_Kuznetsov/tests/qc_test.t
new file mode 100644
index 00000000..b6582c42
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/qc_test.t
@@ -0,0 +1,4 @@
+ $ ./qc_tests.exe --seed 42
+ random seed: 42
+ ================================================================================
+ success (ran 5 tests)
diff --git a/CSharpStrange_Kuznetsov/tests/qc_tests.ml b/CSharpStrange_Kuznetsov/tests/qc_tests.ml
new file mode 100644
index 00000000..b7fa88f6
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/qc_tests.ml
@@ -0,0 +1,939 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open C_sharp_strange_lib
+open Parser
+open QCheck
+open Gen
+open Ast
+
+(* Basic generators *)
+
+(* Basic characters and strings *)
+let gen_alpha = Gen.(oneof [ char_range 'a' 'z'; char_range 'A' 'Z' ])
+let gen_digit = Gen.char_range '0' '9'
+let gen_ident_char = Gen.(oneof [ gen_alpha; gen_digit; return '_' ])
+
+let printable_chars =
+ [ 'a'
+ ; 'b'
+ ; 'c'
+ ; 'd'
+ ; 'e'
+ ; 'f'
+ ; 'g'
+ ; 'h'
+ ; 'i'
+ ; 'j'
+ ; 'k'
+ ; 'l'
+ ; 'm'
+ ; 'n'
+ ; 'o'
+ ; 'p'
+ ; 'q'
+ ; 'r'
+ ; 's'
+ ; 't'
+ ; 'u'
+ ; 'v'
+ ; 'w'
+ ; 'x'
+ ; 'y'
+ ; 'z'
+ ; 'A'
+ ; 'B'
+ ; 'C'
+ ; 'D'
+ ; 'E'
+ ; 'F'
+ ; 'G'
+ ; 'H'
+ ; 'I'
+ ; 'J'
+ ; 'K'
+ ; 'L'
+ ; 'M'
+ ; 'N'
+ ; 'O'
+ ; 'P'
+ ; 'Q'
+ ; 'R'
+ ; 'S'
+ ; 'T'
+ ; 'U'
+ ; 'V'
+ ; 'W'
+ ; 'X'
+ ; 'Y'
+ ; 'Z'
+ ; '0'
+ ; '1'
+ ; '2'
+ ; '3'
+ ; '4'
+ ; '5'
+ ; '6'
+ ; '7'
+ ; '8'
+ ; '9'
+ ]
+;;
+
+let safe_chars =
+ printable_chars
+ @ [ ' '; '_'; '-'; '+'; '*'; '/'; '='; '('; ')'; '.'; ','; ';'; ':'; '!'; '?' ]
+;;
+
+(* Generator for safe string characters *)
+let gen_safe_char = Gen.oneof (List.map return safe_chars)
+
+(* Char generator *)
+let gen_printable_char = Gen.oneof (List.map Gen.return printable_chars)
+
+(* Identifier generator with reserved words check *)
+let reserved =
+ [ "true"
+ ; "false"
+ ; "if"
+ ; "else"
+ ; "while"
+ ; "public"
+ ; "static"
+ ; "void"
+ ; "string"
+ ; "char"
+ ; "int"
+ ; "bool"
+ ; "for"
+ ; "null"
+ ; "new"
+ ; "return"
+ ; "break"
+ ; "continue"
+ ; "class"
+ ; "async"
+ ; "await"
+ ]
+;;
+
+let gen_ident =
+ let gen_first_char = Gen.char_range 'a' 'z' in
+ let gen_rest = Gen.list_size (Gen.int_bound 8) gen_ident_char in
+ let gen_name =
+ Gen.map2
+ (fun first rest ->
+ let chars = first :: rest in
+ let buf = Buffer.create 10 in
+ List.iter (Buffer.add_char buf) chars;
+ Buffer.contents buf)
+ gen_first_char
+ gen_rest
+ in
+ Gen.map (fun name -> if List.mem name reserved then Id "x" else Id name) gen_name
+;;
+
+(* Types and values *)
+
+let gen_base_type =
+ Gen.oneof [ return TypeInt; return TypeChar; return TypeBool; return TypeString ]
+;;
+
+let gen_full_type =
+ Gen.oneof [ Gen.map (fun bt -> TypeBase bt) gen_base_type; return TypeVoid ]
+;;
+
+let gen_modifier = Gen.oneof [ return MPublic; return MStatic; return MAsync ]
+let gen_modifiers = Gen.(list_size (0 -- 2) gen_modifier)
+
+(* Expression generator with type support *)
+
+type type_env =
+ { variables : (ident * _type) list
+ ; functions : (ident * (_type list * _type)) list
+ ; depth : int
+ }
+
+let empty_env = { variables = []; functions = []; depth = 3 }
+
+let gen_safe_string =
+ Gen.map
+ (fun chars -> String.of_seq (List.to_seq chars))
+ (Gen.list_size (Gen.int_bound 10) gen_safe_char)
+;;
+
+(* Expression generator (full, including assignments) *)
+
+let rec gen_expr env expected_type =
+ if env.depth <= 0
+ then gen_base_expr env expected_type
+ else (
+ let env' = { env with depth = env.depth - 1 } in
+ match expected_type with
+ | TypeVoid ->
+ (* For void with non-zero depth try calling functions *)
+ (match env.functions with
+ | [] -> gen_base_expr env expected_type
+ | _ -> gen_funcall env' TypeVoid)
+ | TypeBase _ ->
+ Gen.oneof_weighted
+ [ 3, gen_binop_expr expected_type env'
+ ; 2, gen_unop_expr env' expected_type
+ ; 2, gen_funcall env' expected_type
+ ; 1, gen_id_expr env expected_type
+ ])
+
+and gen_expr_no_assign env expected_type =
+ if env.depth <= 0
+ then gen_base_expr_no_assign env expected_type
+ else (
+ let env' = { env with depth = env.depth - 1 } in
+ match expected_type with
+ | TypeVoid ->
+ (* For void with non-zero depth try calling functions *)
+ (match env.functions with
+ | [] -> gen_base_expr_no_assign env expected_type
+ | _ -> gen_funcall env' TypeVoid)
+ | TypeBase _ ->
+ Gen.oneof_weighted
+ [ 3, gen_binop_expr_no_assign expected_type env'
+ ; 2, gen_unop_expr_no_assign env' expected_type
+ ; 2, gen_funcall env' expected_type
+ ; 1, gen_id_expr_no_assign env expected_type
+ ])
+
+(* Base expressions for shallow depth *)
+and gen_base_expr env = function
+ | TypeBase TypeInt ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun i -> EValue (ValInt i)) (int_bound 100)
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeBool ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun b -> EValue (ValBool b)) bool
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeChar ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun c -> EValue (ValChar c)) gen_printable_char
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeString ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun s -> EValue (ValString s)) gen_safe_string
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeVoid ->
+ (* For void always generate assignment *)
+ Gen.(
+ gen_base_type
+ >>= fun typ ->
+ gen_ident
+ >>= fun id ->
+ let var_type = TypeBase typ in
+ gen_expr_no_assign env var_type >>= fun e -> return (EBinOp (OpAssign, EId id, e)))
+
+(* Identifier generator with type awareness *)
+and gen_id_expr env expected_type =
+ match expected_type with
+ | TypeBase _ ->
+ let vars_of_type = List.filter (fun (_, t) -> t = expected_type) env.variables in
+ (match vars_of_type with
+ | [] ->
+ (* If no variables of required type, generate a new one with correct type *)
+ Gen.map (fun id -> EId id) gen_ident
+ | vars ->
+ let var_ids = List.map fst vars in
+ Gen.oneof (List.map (fun id -> Gen.return (EId id)) var_ids))
+ | TypeVoid ->
+ (* For void always generate assignment *)
+ Gen.(
+ gen_base_type
+ >>= fun typ ->
+ gen_ident
+ >>= fun id ->
+ let var_type = TypeBase typ in
+ gen_expr_no_assign env var_type >>= fun e -> return (EBinOp (OpAssign, EId id, e)))
+
+(* Binary operations *)
+and gen_binop_expr expected_type env =
+ match expected_type with
+ | TypeBase TypeInt ->
+ let int_ops = [ OpAdd; OpSub; OpMul; OpDiv; OpMod ] in
+ let op_gen = Gen.oneof (List.map return int_ops) in
+ Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ op_gen
+ (gen_expr env (TypeBase TypeInt))
+ (gen_expr env (TypeBase TypeInt))
+ | TypeBase TypeBool ->
+ let comparison_ops =
+ [ OpEqual; OpNonEqual; OpLess; OpMore; OpLessEqual; OpMoreEqual ]
+ in
+ let logical_ops = [ OpAnd; OpOr ] in
+ Gen.oneof_weighted
+ [ ( 2
+ , Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return comparison_ops))
+ (gen_expr env (TypeBase TypeInt))
+ (gen_expr env (TypeBase TypeInt)) )
+ ; ( 1
+ , Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return logical_ops))
+ (gen_expr env (TypeBase TypeBool))
+ (gen_expr env (TypeBase TypeBool)) )
+ ]
+ | TypeBase TypeChar | TypeBase TypeString ->
+ (* For char and string only comparison *)
+ let comp_ops = [ OpEqual; OpNonEqual ] in
+ Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return comp_ops))
+ (gen_expr env expected_type)
+ (gen_expr env expected_type)
+ | TypeVoid -> gen_expr env TypeVoid
+
+(* Unary operations *)
+and gen_unop_expr env expected_type =
+ match expected_type with
+ | TypeBase TypeBool ->
+ Gen.map2 (fun op e -> EUnOp (op, e)) (return OpNot) (gen_expr env (TypeBase TypeBool))
+ | TypeBase TypeInt ->
+ Gen.oneof_weighted
+ [ ( 2
+ , Gen.map2
+ (fun op e -> EUnOp (op, e))
+ (return OpNeg)
+ (gen_expr env (TypeBase TypeInt)) )
+ ; ( 1
+ , Gen.map2
+ (fun op id -> EUnOp (op, EId id))
+ (Gen.oneof [ return OpNot; return OpNeg ])
+ gen_ident )
+ ]
+ | _ -> gen_expr env expected_type
+
+(* Function calls *)
+and gen_funcall env expected_type =
+ let funcs_of_type =
+ List.filter (fun (_, (_, ret)) -> ret = expected_type) env.functions
+ in
+ match funcs_of_type with
+ | [] -> gen_base_expr env expected_type
+ | funcs_list ->
+ let gen_func = Gen.oneof (List.map (fun (id, _) -> return (EId id)) funcs_list) in
+ Gen.map2
+ (fun f args -> EFuncCall (f, Args args))
+ gen_func
+ (gen_args env expected_type)
+
+and gen_args env expected_type =
+ Gen.(int_bound 2 >>= fun count -> list_size (return count) (gen_expr env expected_type))
+
+(* Expression generator WITHOUT assignments (for initializers) *)
+
+(* Base expressions WITHOUT assignments for shallow depth *)
+and gen_base_expr_no_assign _ = function
+ | TypeBase TypeInt ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun i -> EValue (ValInt i)) (int_bound 100)
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeBool ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun b -> EValue (ValBool b)) bool
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeChar ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun c -> EValue (ValChar c)) gen_printable_char
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeBase TypeString ->
+ Gen.oneof_weighted
+ [ 4, Gen.map (fun s -> EValue (ValString s)) gen_safe_string
+ ; 1, Gen.map (fun id -> EId id) gen_ident
+ ]
+ | TypeVoid ->
+ (* For void at zero depth - use existing variables
+ or create new ones through declarations in gen_decl_stmt *)
+ Gen.oneof
+ [ Gen.map (fun id -> EId id) gen_ident
+ ; Gen.map (fun i -> EValue (ValInt i)) (int_bound 100)
+ ; Gen.map (fun b -> EValue (ValBool b)) bool
+ ; Gen.map (fun c -> EValue (ValChar c)) gen_printable_char
+ ; Gen.map (fun s -> EValue (ValString s)) gen_safe_string
+ ]
+
+(* Identifier generator with type awareness (no assignments) *)
+and gen_id_expr_no_assign env expected_type =
+ match expected_type with
+ | TypeBase _ ->
+ let vars_of_type = List.filter (fun (_, t) -> t = expected_type) env.variables in
+ (match vars_of_type with
+ | [] ->
+ (* If no variables of required type, generate a value *)
+ gen_base_expr_no_assign env expected_type
+ | vars ->
+ let var_ids = List.map fst vars in
+ Gen.oneof (List.map (fun id -> Gen.return (EId id)) var_ids))
+ | TypeVoid ->
+ (* For void generate a value *)
+ gen_base_expr_no_assign env TypeVoid
+
+(* Unary operations without assignment *)
+and gen_unop_expr_no_assign env expected_type =
+ match expected_type with
+ | TypeBase TypeBool ->
+ Gen.map2
+ (fun op e -> EUnOp (op, e))
+ (return OpNot)
+ (gen_expr_no_assign env (TypeBase TypeBool))
+ | TypeBase TypeInt ->
+ Gen.oneof_weighted
+ [ ( 2
+ , Gen.map2
+ (fun op e -> EUnOp (op, e))
+ (return OpNeg)
+ (gen_expr_no_assign env (TypeBase TypeInt)) )
+ ; ( 1
+ , Gen.map2
+ (fun op id -> EUnOp (op, EId id))
+ (Gen.oneof [ return OpNot; return OpNeg ])
+ gen_ident )
+ ]
+ | _ -> gen_expr_no_assign env expected_type
+
+(* Binary operations without assignment *)
+and gen_binop_expr_no_assign expected_type env =
+ match expected_type with
+ | TypeBase TypeInt ->
+ let int_ops = [ OpAdd; OpSub; OpMul; OpDiv; OpMod ] in
+ let op_gen = Gen.oneof (List.map return int_ops) in
+ Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ op_gen
+ (gen_expr_no_assign env (TypeBase TypeInt))
+ (gen_expr_no_assign env (TypeBase TypeInt))
+ | TypeBase TypeBool ->
+ let comparison_ops =
+ [ OpEqual; OpNonEqual; OpLess; OpMore; OpLessEqual; OpMoreEqual ]
+ in
+ let logical_ops = [ OpAnd; OpOr ] in
+ Gen.oneof_weighted
+ [ ( 2
+ , Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return comparison_ops))
+ (gen_expr_no_assign env (TypeBase TypeInt))
+ (gen_expr_no_assign env (TypeBase TypeInt)) )
+ ; ( 1
+ , Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return logical_ops))
+ (gen_expr_no_assign env (TypeBase TypeBool))
+ (gen_expr_no_assign env (TypeBase TypeBool)) )
+ ]
+ | TypeBase TypeChar | TypeBase TypeString ->
+ let comp_ops = [ OpEqual; OpNonEqual ] in
+ Gen.map3
+ (fun op l r -> EBinOp (op, l, r))
+ (Gen.oneof (List.map return comp_ops))
+ (gen_expr_no_assign env expected_type)
+ (gen_expr_no_assign env expected_type)
+ | TypeVoid -> gen_expr_no_assign env TypeVoid
+;;
+
+(* Type for assignment result *)
+
+type assign_result =
+ | AssignExpr of expr
+ | AssignBlock of stmt
+
+(* Statement generator *)
+
+let rec gen_stmt env return_type =
+ if env.depth <= 0
+ then gen_simple_stmt env return_type
+ else (
+ let env' = { env with depth = env.depth - 1 } in
+ Gen.oneof_weighted
+ [ 2, gen_decl_stmt env'
+ ; 2, gen_expr_stmt env'
+ ; 2, gen_if_stmt env' return_type
+ ; 2, gen_while_stmt env' return_type
+ ; 1, gen_for_stmt env' return_type
+ ; 1, gen_return_stmt env' return_type
+ ; 1, gen_block_stmt env' return_type
+ ; 1, gen_break_continue_stmt
+ ])
+
+and gen_simple_stmt env return_type =
+ Gen.oneof [ gen_decl_stmt env; gen_expr_stmt env; gen_return_stmt env return_type ]
+
+and gen_decl_stmt env =
+ Gen.(
+ gen_base_type
+ >>= fun typ ->
+ gen_ident
+ >>= fun id ->
+ (* Generate initializer of correct type WITHOUT assignments *)
+ let expr_gen =
+ match typ with
+ | TypeInt -> gen_expr_no_assign env (TypeBase TypeInt)
+ | TypeChar -> gen_expr_no_assign env (TypeBase TypeChar)
+ | TypeBool -> gen_expr_no_assign env (TypeBase TypeBool)
+ | TypeString -> gen_expr_no_assign env (TypeBase TypeString)
+ in
+ (* Always generate an initializer *)
+ expr_gen
+ >>= fun init_expr ->
+ (* Add variable to environment (for subsequent statements) *)
+ let new_var = id, TypeBase typ in
+ let _ = { env with variables = new_var :: env.variables } in
+ return (SDecl (Var (TypeVar (TypeBase typ), id), Some init_expr)))
+
+(* Assignment generator *)
+and gen_assign_expr env : assign_result Gen.t =
+ match env.variables with
+ | [] ->
+ (* If no variables, first create a declaration *)
+ Gen.(
+ gen_base_type
+ >>= fun typ ->
+ gen_ident
+ >>= fun id ->
+ let var_type = TypeBase typ in
+ gen_expr env var_type
+ >>= fun e ->
+ return
+ (AssignBlock
+ (SBlock
+ [ SDecl (Var (TypeVar var_type, id), Some e)
+ ; SExpr (EBinOp (OpAssign, EId id, EId id))
+ ])))
+ | vars ->
+ let var_ids = List.map fst vars in
+ Gen.(
+ oneof
+ (List.map
+ (fun id ->
+ (* Choose appropriate type for expression *)
+ let typ = List.assoc id env.variables in
+ gen_expr env typ
+ >>= fun e -> return (AssignExpr (EBinOp (OpAssign, EId id, e))))
+ var_ids))
+
+(* Function call generator with return value ignored *)
+and gen_funcall_ignore_return env : expr Gen.t =
+ let non_void_funcs = List.filter (fun (_, (_, ret)) -> ret <> TypeVoid) env.functions in
+ match non_void_funcs with
+ | [] ->
+ gen_assign_expr env
+ >>= (function
+ | AssignExpr e -> Gen.return e
+ | AssignBlock (SBlock stmts) ->
+ (* Find expression in block *)
+ let rec find_expr = function
+ | [] -> gen_base_expr env (TypeBase TypeInt)
+ | SExpr e :: _ -> Gen.return e
+ | _ :: rest -> find_expr rest
+ in
+ find_expr stmts
+ | _ -> gen_base_expr env (TypeBase TypeInt))
+ (* fallback *)
+ | funcs_list ->
+ Gen.(
+ oneof
+ (List.map
+ (fun (func_id, (param_types, _)) ->
+ let rec gen_args_for_params params acc =
+ match params with
+ | [] -> return (List.rev acc)
+ | t :: ts ->
+ (match t with
+ | TypeBase bt ->
+ gen_expr env (TypeBase bt)
+ >>= fun arg -> gen_args_for_params ts (arg :: acc)
+ | _ -> gen_args_for_params ts acc (* skip unsupported types *))
+ in
+ gen_args_for_params param_types []
+ >>= fun args -> return (EFuncCall (EId func_id, Args args)))
+ funcs_list))
+
+(* Expression statements generator *)
+and gen_expr_stmt env : stmt Gen.t =
+ Gen.(
+ oneof_weighted
+ [ ( 4
+ , gen_assign_expr env
+ >>= function
+ | AssignExpr e -> return (SExpr e)
+ | AssignBlock stmt -> return stmt )
+ ; (4, gen_funcall env TypeVoid >>= fun e -> return (SExpr e))
+ ; ( 2
+ , if List.exists (fun (_, (_, ret)) -> ret <> TypeVoid) env.functions
+ then gen_funcall_ignore_return env >>= fun e -> return (SExpr e)
+ else
+ gen_assign_expr env
+ >>= function
+ | AssignExpr e -> return (SExpr e)
+ | AssignBlock stmt -> return stmt )
+ ])
+
+and gen_if_stmt env return_type =
+ Gen.map3
+ (fun cond then_stmt else_stmt -> SIf (cond, then_stmt, else_stmt))
+ (gen_expr env (TypeBase TypeBool))
+ (gen_stmt env return_type)
+ (Gen.option (gen_stmt env return_type))
+
+and gen_while_stmt env return_type =
+ Gen.map2
+ (fun cond body -> SWhile (cond, body))
+ (gen_expr env (TypeBase TypeBool))
+ (gen_loop_body env return_type)
+
+and gen_for_stmt env return_type =
+ Gen.(
+ option (gen_decl_stmt env)
+ >>= fun init ->
+ option (gen_expr env (TypeBase TypeBool))
+ >>= fun cond ->
+ (* Increment can only be ++ or -- operations *)
+ (if cond <> None
+ then
+ oneof_weighted [ 2, option (gen_unop_expr env (TypeBase TypeInt)); 1, return None ]
+ else return None)
+ >>= fun incr ->
+ gen_loop_body env return_type >>= fun body -> return (SFor (init, cond, incr, body)))
+
+and gen_loop_body env return_type =
+ Gen.oneof_weighted
+ [ 3, gen_stmt env return_type; 1, return SBreak; 1, return SContinue ]
+
+and gen_return_stmt env return_type =
+ match return_type with
+ | TypeVoid -> return (SReturn None)
+ | _ -> Gen.map (fun e -> SReturn (Some e)) (gen_expr env return_type)
+
+and gen_block_stmt env return_type =
+ Gen.map (fun stmts -> SBlock stmts) (Gen.list_size (1 -- 3) (gen_stmt env return_type))
+
+and gen_break_continue_stmt = Gen.oneof [ return SBreak; return SContinue ]
+
+(* Class and program generators *)
+
+let rec has_return = function
+ | SReturn _ -> true
+ | SBlock stmts -> List.exists has_return stmts
+ | SIf (_, then_stmt, Some else_stmt) -> has_return then_stmt || has_return else_stmt
+ | SIf (_, then_stmt, None) -> has_return then_stmt
+ | SWhile (_, body) -> has_return body
+ | SFor (_, _, _, body) -> has_return body
+ | _ -> false
+;;
+
+let ensure_return stmt return_type =
+ if return_type <> TypeVoid && not (has_return stmt)
+ then (
+ let default_return =
+ match return_type with
+ | TypeBase TypeInt -> SReturn (Some (EValue (ValInt 0)))
+ | TypeBase TypeBool -> SReturn (Some (EValue (ValBool false)))
+ | TypeBase TypeChar -> SReturn (Some (EValue (ValChar 'a')))
+ | TypeBase TypeString -> SReturn (Some (EValue (ValString "")))
+ | _ -> SReturn None
+ in
+ match stmt with
+ | SBlock stmts -> SBlock (stmts @ [ default_return ])
+ | _ -> SBlock [ stmt; default_return ])
+ else if return_type = TypeVoid && not (has_return stmt)
+ then (
+ match stmt with
+ | SBlock stmts -> SBlock stmts
+ | _ -> stmt)
+ else stmt
+;;
+
+let gen_param =
+ Gen.map2 (fun typ id -> Var (TypeVar (TypeBase typ), id)) gen_base_type gen_ident
+;;
+
+let gen_method env depth =
+ Gen.(
+ gen_modifiers
+ >>= fun modifiers ->
+ gen_full_type
+ >>= fun return_type ->
+ gen_ident
+ >>= fun id ->
+ (* Generate parameters *)
+ int_bound 3
+ >>= fun param_count ->
+ list_size (return param_count) gen_param
+ >>= fun params ->
+ (* Update environment with parameters *)
+ let env_with_params =
+ List.fold_left
+ (fun env (Var (TypeVar t, id')) ->
+ { env with variables = (id', t) :: env.variables })
+ env
+ params
+ in
+ (* Generate method body considering return type *)
+ gen_stmt { env_with_params with depth } return_type
+ >>= fun body ->
+ (* Check and add return if needed *)
+ let valid_body = ensure_return body return_type in
+ return (Method (modifiers, return_type, id, Params params, valid_body)))
+;;
+
+let gen_field env =
+ Gen.(
+ gen_modifiers
+ >>= fun modifiers ->
+ gen_base_type
+ >>= fun typ ->
+ gen_ident
+ >>= fun id ->
+ (* Generate initializer of correct type WITHOUT assignments *)
+ let expr_gen =
+ match typ with
+ | TypeInt -> gen_expr_no_assign { env with depth = 1 } (TypeBase TypeInt)
+ | TypeChar -> gen_expr_no_assign { env with depth = 1 } (TypeBase TypeChar)
+ | TypeBool -> gen_expr_no_assign { env with depth = 1 } (TypeBase TypeBool)
+ | TypeString -> gen_expr_no_assign { env with depth = 1 } (TypeBase TypeString)
+ in
+ (* Always generate an initializer *)
+ expr_gen
+ >>= fun init -> return (VarField (modifiers, TypeVar (TypeBase typ), id, Some init)))
+;;
+
+let gen_class depth =
+ let env = { empty_env with depth } in
+ let rec build_members env acc count =
+ if count <= 0
+ then Gen.return (List.rev acc, env)
+ else
+ Gen.(
+ bool
+ >>= fun is_field ->
+ if is_field
+ then gen_field env >>= fun field -> build_members env (field :: acc) (count - 1)
+ else
+ gen_method env depth
+ >>= fun meth ->
+ match meth with
+ | Method (_, ret_type, id, Params params, _) ->
+ let param_types = List.map (fun (Var (TypeVar t, _)) -> t) params in
+ let env' =
+ { env with functions = (id, (param_types, ret_type)) :: env.functions }
+ in
+ build_members env' (meth :: acc) (count - 1)
+ | _ -> build_members env (meth :: acc) (count - 1))
+ in
+ Gen.(
+ int_range 1 3
+ >>= fun member_count ->
+ build_members env [] member_count
+ >>= fun (members, _) ->
+ gen_modifiers
+ >>= fun modifiers -> gen_ident >>= fun id -> return (Class (modifiers, id, members)))
+;;
+
+let gen_program depth = Gen.map (fun cls -> Program cls) (gen_class depth)
+
+(* Helper functions *)
+
+let expr_to_code_string expr = Format.asprintf "%a" Prettyprinter.pp_expr expr
+let program_to_code_string prog = Format.asprintf "%a" Prettyprinter.pp_prog prog
+
+let compare_expr_structure e1 e2 =
+ match e1, e2 with
+ | EValue v1, EValue v2 ->
+ (match v1, v2 with
+ | ValInt _, ValInt _
+ | ValChar _, ValChar _
+ | ValBool _, ValBool _
+ | ValString _, ValString _
+ | ValNull, ValNull -> true
+ | _ -> false)
+ | EId _, EId _ -> true
+ | EBinOp (op1, _, _), EBinOp (op2, _, _) -> op1 = op2
+ | EUnOp (op1, _), EUnOp (op2, _) -> op1 = op2
+ | EFuncCall (_, Args a1), EFuncCall (_, Args a2) -> List.length a1 = List.length a2
+ | _ -> false
+;;
+
+(* QCheck generators *)
+
+let test_count = 200
+
+let expr_arbitrary depth =
+ let env = { empty_env with depth } in
+ let gen =
+ Gen.oneof
+ [ gen_expr env (TypeBase TypeInt)
+ ; gen_expr env (TypeBase TypeBool)
+ ; gen_expr env (TypeBase TypeChar)
+ ; gen_expr env (TypeBase TypeString)
+ ; gen_expr env TypeVoid
+ ]
+ in
+ QCheck.make ~print:show_expr gen
+;;
+
+let program_arbitrary depth = QCheck.make ~print:show_program (gen_program depth)
+
+(* Tests *)
+
+let prop_roundtrip_expr =
+ Test.make
+ ~name:"Expression roundtrip: show -> parse -> show"
+ ~count:test_count
+ (expr_arbitrary 2)
+ (fun expr ->
+ let code_str = expr_to_code_string expr in
+ match Angstrom.parse_string ~consume:Angstrom.Consume.All parse_ops code_str with
+ | Ok expr' ->
+ let code_str' = expr_to_code_string expr' in
+ code_str = code_str'
+ | Error e ->
+ let () =
+ Format.eprintf
+ "\n\
+ @[Expression parse failed:@ Input AST: %s@ Output code: %s@ Error: %s@]"
+ (show_expr expr)
+ code_str
+ e
+ in
+ false)
+;;
+
+let prop_roundtrip_program =
+ Test.make
+ ~name:"Program roundtrip: show -> parse -> show"
+ ~count:test_count
+ (program_arbitrary 1)
+ (fun prog ->
+ let code_str = program_to_code_string prog in
+ match Angstrom.parse_string ~consume:Angstrom.Consume.All parse_prog code_str with
+ | Ok prog' ->
+ let code_str' = program_to_code_string prog' in
+ code_str = code_str'
+ | Error e ->
+ let () =
+ Format.eprintf
+ "\n@[Program parse failed:@ Input AST: %s@ Output code: %s@ Error: %s@]"
+ (show_program prog)
+ code_str
+ e
+ in
+ false)
+;;
+
+let prop_operator_precedence =
+ Test.make
+ ~name:"Operator precedence is preserved"
+ ~count:test_count
+ (expr_arbitrary 2)
+ (fun expr ->
+ let code_str = expr_to_code_string expr in
+ match Angstrom.parse_string ~consume:Angstrom.Consume.All parse_ops code_str with
+ | Ok expr' -> compare_expr_structure expr expr'
+ | Error e ->
+ let () =
+ Format.eprintf
+ "\n\
+ @[Precedence test parse failed:@ Expression AST: %s@ Code: %s@ Error: \
+ %s@]"
+ (show_expr expr)
+ code_str
+ e
+ in
+ false)
+;;
+
+let prop_parse_errors =
+ let gen_invalid =
+ Gen.oneof
+ [ return "??"
+ ; return "+++"
+ ; return "()"
+ ; return ".;"
+ ; return "==="
+ ; return "!"
+ ; return "&"
+ ; return "|"
+ ; return "*"
+ ; return "/"
+ ; return "class"
+ ; return "if"
+ ; return "while"
+ ]
+ in
+ Test.make
+ ~name:"Parser returns error on invalid input"
+ ~count:test_count
+ (QCheck.make ~print:(fun s -> s) gen_invalid)
+ (fun str ->
+ match Angstrom.parse_string ~consume:Angstrom.Consume.All parse_ops str with
+ | Error _ -> true
+ | Ok expr ->
+ Format.eprintf
+ "\n\
+ @[Parser should have failed but succeeded:@ Input: %s@ Parsed as AST: \
+ %s@]"
+ str
+ (show_expr expr);
+ false)
+;;
+
+let prop_parse_no_crash =
+ Test.make
+ ~name:"Parser does not crash on valid expressions"
+ ~count:test_count
+ (expr_arbitrary 2)
+ (fun expr ->
+ let code_str = expr_to_code_string expr in
+ match Angstrom.parse_string ~consume:Angstrom.Consume.All parse_ops code_str with
+ | Ok _ -> true
+ | Error e ->
+ Format.eprintf
+ "\n\
+ @[Parser failed on valid expression (not a crash):@ Input AST: %s@ \
+ Generated code: %s@ Error: %s@]"
+ (show_expr expr)
+ code_str
+ e;
+ true)
+;;
+
+(* Test runner with command line argument handling *)
+
+let tests =
+ [ prop_parse_no_crash
+ ; prop_roundtrip_expr
+ ; prop_operator_precedence
+ ; prop_roundtrip_program
+ ; prop_parse_errors
+ ]
+;;
+
+(* Function to run tests *)
+let run () = QCheck_base_runner.run_tests tests
+
+(* Command line argument handling and execution *)
+let () =
+ Arg.parse
+ [ "--seed", Arg.Int QCheck_base_runner.set_seed, " Set random seed" ]
+ (fun _ -> ())
+ "Usage: qt_tests.exe [options]";
+ let exit_code = run () in
+ if exit_code <> 0 then exit exit_code
+;;
diff --git a/CSharpStrange_Kuznetsov/tests/qc_tests.mli b/CSharpStrange_Kuznetsov/tests/qc_tests.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/qc_tests.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
diff --git a/CSharpStrange_Kuznetsov/tests/typecheck_tests.ml b/CSharpStrange_Kuznetsov/tests/typecheck_tests.ml
new file mode 100644
index 00000000..ffbc6853
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/typecheck_tests.ml
@@ -0,0 +1,230 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)
+
+open C_sharp_strange_lib.Typecheck
+open C_sharp_strange_lib.Parser
+open C_sharp_strange_lib.Ast
+open C_sharp_strange_lib.Common
+
+let show_wrap = function
+ | Some (Program x) ->
+ (match typecheck x with
+ | _, Result.Ok _ -> Format.print_string "Ok!\n"
+ | _, Result.Error e -> Format.printf "%a\n%!" pp_error e)
+ | _ -> Format.print_string "Parsing error\n"
+;;
+
+let print_tc p str = show_wrap (parse_option p str)
+let test_typecheck = print_tc parse_prog
+
+let%expect_test "Factorial" =
+ test_typecheck
+ {|
+ class Program {
+ int Fac(int num) {
+ if (num == 1) {
+ return 1;
+ }
+ else
+ {
+ return num * Fac(num - 1);
+ }
+ }
+ public static int Main() {
+ return Fac(5);
+ }
+ } |};
+ [%expect
+ {|
+ Ok! |}]
+;;
+
+let%expect_test "Wrong factorial" =
+ test_typecheck
+ {|
+ class Program {
+ int Fac(int num) {
+ if (num == 1) {
+ return "one";
+ }
+ }
+ } |};
+ [%expect
+ {|
+ (TCError (OtherError "Returned type does not match the function type")) |}]
+;;
+
+let%expect_test "Already declared variable" =
+ test_typecheck
+ {|
+ class Program {
+ int a = 5;
+ int b = 9;
+ int a = 9;
+ } |};
+ [%expect
+ {|
+ (TCError (OtherError "This variable is already declared")) |}]
+;;
+
+let%expect_test "Invalid value" =
+ test_typecheck
+ {|
+ class Program {
+ public static int Main() {
+ int a;
+ int b = a -1 + 4;
+ return b;
+ }
+ } |};
+ [%expect
+ {|
+ (TCError (OtherError "Variable not found: a"))|}]
+;;
+
+let%expect_test "Checking fields" =
+ test_typecheck
+ {|
+ class Program {
+ int b = 9;
+ int c = b * 67;
+ int a = (50 % 2) + b - c;
+ bool r = (a != b * c) || (a >= b) && (a == c +90);
+ string s = "ok";
+ char h = 'a';
+
+ void M() {
+ a = 5;
+ r = s != "kkkk" && (190%22 == 100 * -2/5);
+ }
+ } |};
+ [%expect
+ {|
+ Ok! |}]
+;;
+
+let%expect_test "String + int" =
+ test_typecheck
+ {|
+ class Program {
+ string a = "5";
+ int c = 9 + a;
+ } |};
+ [%expect
+ {|
+ (TCError TypeMismatch) |}]
+;;
+
+let%expect_test "While" =
+ test_typecheck
+ {|
+ class Program {
+ public static int Main() {
+ int count = 0;
+ bool b = true;
+ while(true) {
+ if (count != 2) {
+ count = count + 1;
+ b = b && false;
+ }
+ else if (b == false){
+ return -1;
+ }
+ else {
+ return 0;
+ }
+ }
+ }
+ } |};
+ [%expect
+ {|
+ Ok! |}]
+;;
+
+let%expect_test "For" =
+ test_typecheck
+ {|
+ class Program {
+ int n = 10;
+ int count = 7% 2*67;
+ public static int Main() {
+ for (int i = 0; i < n; i=i+1) {
+ for (int j = 1;;) {
+ for (;j != n; j = j + 2) {
+ for (;;) {
+ count = count + i + j;
+ }
+ }
+ }
+ }
+ return count;
+ }
+ } |};
+ [%expect
+ {|
+ Ok! |}]
+;;
+
+let%expect_test "Wrong main" =
+ test_typecheck
+ {|
+ class Program {
+ public async void Main() {}
+ }
+ |};
+ [%expect
+ {|
+ (TCError
+ (OtherError "Main must be static, non-async, no params, return int/void")) |}]
+;;
+
+let%expect_test "Already declared function" =
+ test_typecheck
+ {|
+ class Program {
+ void Test() {}
+ int a = 9;
+ void Test() {}
+ } |};
+ [%expect
+ {|
+ (TCError (OtherError "This variable is already declared")) |}]
+;;
+
+let%expect_test "Function type mismatch" =
+ test_typecheck
+ {|
+ class Program {
+ public void a(int n, int m){
+ return n+m;
+ }
+ }|};
+ [%expect
+ {|
+ (TCError TypeMismatch) |}]
+;;
+
+let%expect_test "Factorial with writeline" =
+ test_typecheck
+ {|
+ class Program {
+ int Fac(int num) {
+ if (num == 1) {
+ return 1;
+ }
+ else
+ {
+ return num * Fac(num - 1);
+ }
+ }
+ public static int Main() {
+ int result = Fac(5);
+ System.Console.WriteLine(result);
+ return result;
+ }
+ } |};
+ [%expect
+ {|
+ Ok! |}]
+;;
diff --git a/CSharpStrange_Kuznetsov/tests/typecheck_tests.mli b/CSharpStrange_Kuznetsov/tests/typecheck_tests.mli
new file mode 100644
index 00000000..6b453b16
--- /dev/null
+++ b/CSharpStrange_Kuznetsov/tests/typecheck_tests.mli
@@ -0,0 +1,3 @@
+(** Copyright 2026, Dmitrii Kuznetsov *)
+
+(** SPDX-License-Identifier: LGPL-3.0-or-later *)