diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..4f928f0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,82 @@ +name: 🐛 Bug Report +description: Report a bug +title: (bug report summary) +labels: Bug +body: + - type: textarea + id: description + attributes: + label: Describe the bug + description: What is the problem? A clear and concise description of the bug. + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: What did you expect to happen? + validations: + required: true + + - type: textarea + id: current + attributes: + label: Current Behavior + description: | + What actually happened? + + Please include full errors, uncaught exceptions, stack traces, and relevant logs. + If service/functions responses are relevant, please include wire logs. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Reproduction Steps + description: | + Provide a self-contained, concise snippet of code that can be used to reproduce the issue. + For more complex issues provide a repo with the smallest sample that reproduces the bug. + + Avoid including business logic or unrelated code, it makes diagnosis more difficult. + The code sample should be an SSCCE. See http://sscce.org/ for details. + In short, please provide a code sample that we can copy/paste, run and reproduce. + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Possible Solution + description: Suggest a fix/reason for the bug + validations: + required: false + + - type: textarea + id: context + attributes: + label: Additional Information/Context + description: | + Anything else that might be relevant for troubleshooting this bug. + Providing context helps us come up with a solution that is most useful in the real world. + validations: + required: false + + - type: input + id: version + attributes: + label: Nuru version + description: | + Please make sure to use the latest version of Nuru before reporting any issues as it may have already been fixed. + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment details (OS name and version, etc.) + description: Your operating system (Windows, Linux, Android or MacOS) + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000..7a32203 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,65 @@ +name: 🚀 Feature Request +description: Suggest an idea for this project +title: (feature request summary) +labels: Feature Request +body: + - type: textarea + id: description + attributes: + label: Describe the feature + description: A clear and concise description of the feature you are proposing. + validations: + required: true + + - type: textarea + id: use-case + attributes: + label: Use Case + description: | + Why do you need this feature? For example: "I'm always frustrated when..." + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed Solution + description: Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. + validations: + required: false + + - type: textarea + id: other + attributes: + label: Other Information + description: Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. + validations: + required: false + + - type: checkboxes + id: ack + attributes: + label: Acknowledgements + options: + - label: I may be able to implement this feature request + required: false + + - label: This feature might incur a breaking change + required: false + + - type: input + id: version + attributes: + label: Version used + description: Please provide the version of the repository or tool you are using. + validations: + required: true + + - type: textarea + id: environment + attributes: + label: Environment details (OS name and version, etc.) + description: Your operating system (Linux, Windows, Android or Mac) + validations: + required: true + diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 0000000..92e4962 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,31 @@ + + + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3c26f7f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: Go + +on: + push: + tags: + - "*" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21 + id: go + + - name: Test + run: go mod tidy && make test + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..7f6c282 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,21 @@ +name: Go + +on: + push: + branches: [ main, dev ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21 + + - name: Test + run: go mod tidy && make test + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2443257 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +.DS_Store +*.[56789ao] +*.a[56789o] +*.so +*.pyc +._* +.nfs.* +[56789a].out +*~ +*.orig +*.rej +*.exe +.*.swp +core +*.cgo*.go +*.cgo*.c +_cgo_* +_obj +_test +_testmain.go +/VERSION.cache +/bin/ +/build.out +/doc/articles/wiki/*.bin +/goinstall.log +/last-change +/misc/cgo/life/run.out +/misc/cgo/stdio/run.out +/misc/cgo/testso/main +/pkg/ +/src/*.*/ +/src/cmd/cgo/zdefaultcc.go +/src/cmd/dist/dist +/src/cmd/go/internal/cfg/zdefaultcc.go +/src/cmd/go/internal/cfg/zosarch.go +/src/cmd/internal/objabi/zbootstrap.go +/src/go/build/zcgo.go +/src/go/doc/headscan +/src/runtime/internal/sys/zversion.go +/src/unicode/maketables +/test.out +/test/garbage/*.out +/test/pass.out +/test/run.out +/test/times.out + +#Personal + +testbinaries/ +tests_random/ +nuru +Notes.md +tutorials/en/* +config.json +*local* + +# For Nuru executables +/nuru +/Nuru + +dist/ diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..92e6585 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,47 @@ +project_name: nuru +before: + hooks: + - go mod tidy + - go generate ./... + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + - android + ldflags: + - "-s -w" + ignore: + - goos: android + goarch: 386 + +archives: + - format: tar.gz + name_template: >- + nuru_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}amd64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + format_overrides: + - goos: windows + format: zip + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + +nfpms: + - maintainer: "AvicennaJr" + homepage: "https://nuruprogramming.org" + description: "Nuru is a programming language built from the ground up" + formats: + - deb + file_name_template: "{{ .ProjectName }}.{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}" diff --git a/ABOUT.md b/ABOUT.md new file mode 100644 index 0000000..eb1d523 --- /dev/null +++ b/ABOUT.md @@ -0,0 +1,47 @@ +# NURU PROGRAMMING LANGUAGE + +This page intends to show the origins of Nuru, its purpose, what it can be used for, what it should not be used for and the potential future of the programming language. + +## Background + +This language is the direct child of a programming language called "Monkey Language" made by Thorston Ball. He wrote a book titled "Writing An Interpreter In Go" and the core of the language is based on his book. + +With the knowledge I gained from his book, I saw an opportunity to write a Swahili Programming Language, which would not just be a translation of an already existing one, but an actual standalone interpreted language that can be built from the ground up, where everything can be customized from the syntax, its abilities, its standard library and more. + +Now one may wonder, why a new programming language when there are many other much better programming languages in existence. The answer is, it is a Swahili programming language. And this is significant for two reasons: +- Many are unable to learn programming due to the language barrier. Almost all programming languages in existence are in English and thus a non English speaker would have to first learn English before they can learn a programming language. This makes the effort twice as difficult and Nuru, a native Swahili programming language, intends to make the process of learning programming languages a bit easier. +- Secondly, even if no one does use this programming language, then at least we can say "We do have a fully functional Swahili programming language", and as a person who grew up in Tanzania, this does give me pride. + +## Purpose + +Nuru does not intend to replace any existing programming language. In fact, it does not intend to be used in production at all. Nuru intends to be an educational programming language, a programming language that will make it easy for anyone to get into the world of programming without knowing English. It intends to be simple enough to be taught to kids in primary and highschool and allow them to build interesting tools with it. + +Nuru also hopes to be used by hobbyists and experienced programmers, where by they will find it easy to write scripts in Nuru that will help solve their various tasks or build interesting projects with it. As a matter of fact, someone already made a sudoku solver in Nuru. + +While being simple it also intends to be fully functional. Other than having all the core features required by a programming language, Nuru also has an extensive standard library that will make performing common tasks much easier. Thus, it intends to bring the best of both worlds, simple to use with a lot of features. + +## Philosophy + +Nuru's philosophy is to keep things simple. Everything in Nuru should be consistent and intuitive, from its syntax to the keywords used. On the matter of keywords, Nuru intends to provide keywords that are simple and intuitive that can easily explain what the function or library is for. A more detailed guide on the Nuru's syntax and the proper way of writing Nuru will be provided in the near future. + +Nuru is also community driven. We listen to our community and do our best to implement on the feedback we get from them. + +## Where Not To Use Nuru + +Nuru's performance is worse than python. It has been authored by someone with very limited knowledge in programming. Thus, it is advised to never use Nuruin production code where by any kind of mistakes are critical. Nuru is still very immature and should only be used for educational and hobby projects. + +## Challenges + +The main challenge we have in Nuru is in naming keywords. Since this is something new, there are lack of words that fully describe common programming words. However, we do intend to try our best to select the best keywords, and we often consult with our community when choosing a word. + +## Future Of Nuru + +It is still too early to know how Nuru will evolve, or the way in which the community will use this language. However, what is certain is the core developers will do their best to provide a language that will be enjoyable to learn and code in. We listen to our community and hopefully we will soon have a large number of developers contributing to the language. + +We also hope to see games and GUI applications written in Nuru in the near future... God willing. + +## Final Words + +I am very grateful to the reception of this project, we now have over 150+ downloads and its barely been a month. We hope to fulfill all your expectations and provide something that you will all enjoy to use. We also ask you to bear with us when we make mistakes and correct and advise us on areas where we can do better. I am also grateful to Thorston for writing such an amazing book, and I would recommend anyone who'd want to learn how programming languages work to read his book. + +And finally, I thank Allah for granting us the ability to learn and giving me the ability to make such a project. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eec06e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +VERSION=0.5.1 + +build_linux: + @echo 'building linux binary...' + env GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o nuru + @echo 'shrinking binary...' + ./upx --brute nuru + @echo 'zipping build....' + tar -zcvf nuru_linux_amd64_v${VERSION}.tar.gz nuru + @echo 'cleaning up...' + rm nuru + +build_windows: + @echo 'building windows executable...' + env GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o nuru_windows_amd64_v${VERSION}.exe + @echo 'shrinking build...' + ./upx --brute nuru_windows_amd64_v${VERSION}.exe + +build_mac: + @echo 'building mac binary...' + env GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -o nuru + @echo 'shrinking binary...' + ./upx --brute nuru + @echo 'zipping build...' + tar -zcvf nuru_mac_amd64_v${VERSION}.tar.gz nuru + @echo 'cleaning up...' + rm nuru + +build_android: + @echo 'building android binary' + env GOOS=android GOARCH=arm64 go build -ldflags="-s -w" -o nuru + @echo 'zipping build...' + tar -zcvf nuru_android_arm64_v${VERSION}.tar.gz nuru + @echo 'cleaning up...' + rm nuru + +build_test: + go build -ldflags="-s -w" -o nuru + +dependencies: + @echo 'checking dependencies...' + go mod tidy + +test: + @echo -e '\nTesting Lexer...' + @./gotest --format testname ./lexer/ + @echo -e '\nTesting Parser...' + @./gotest --format testname ./parser/ + @echo -e '\nTesting AST...' + @./gotest --format testname ./ast/ + @echo -e '\nTesting Object...' + @./gotest --format testname ./object/ + @echo -e '\nTesting Evaluator...' + @./gotest --format testname ./evaluator/ + +clean: + go clean diff --git a/README.md b/README.md index e3f6bba..276c936 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,72 @@ -

NURU✨PROGRAMMING✨LANGUAGE

+

NURU🔥PROGRAMMING🔥LANGUAGE

- Nuru Programming Language - Nuru Programming Language - Nuru Programming Language - Nuru Programming Language + Nuru Programming Language + Nuru Programming Language + Nuru Programming Language
- Nuru Programming Language + Nuru Programming Language + Nuru Programming Language + Nuru Programming Language +
+ Nuru Programming Language

A Swahili Programming Language of its kind built from the ground up. ## Installation -To get started download the executables from the release page or follow the -instructions for your device below: +To get started download the executables from the release page or follow the instructions for your device below: ### Linux - - Download the binary: +- Download the binary: ``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_linux_amd64_v0.1.0.tar.gz +curl -O -L https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Linux_amd64.tar.gz ``` - - Extract the file: +- Extract the file to make global available: ``` -tar -xzvf nuru_linux_amd64_v0.1.0.tar.gz +sudo tar -C /usr/local/bin -xzvf nuru_Linux_amd64.tar.gz ``` - - Add it to your $PATH: + +- Confirm installation with: ``` -cp nuru $HOME/bin +nuru -v ``` - - Confirm installation with: + +### MacOs ( Apple silicon Mac ) + +- Download the binary: + + - For apple silicon mac use: + + ``` + curl -O -L https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Darwin_arm64.tar.gz + ``` + + - For apple intel mac use: + + ``` + curl -O -L https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Darwin_amd64.tar.gz + ``` + +- Extract the file to make global available: + + - For apple silicon mac use: + + ``` + sudo tar -C /usr/local/bin -xzvf nuru_Darwin_arm64.tar.gz + ``` + + - For apple intel mac use: + + ``` + sudo tar -C /usr/local/bin -xzvf nuru_Darwin_amd64.tar.gz + ``` + +- Confirm installation with: ``` nuru -v @@ -40,86 +74,111 @@ nuru -v ### Android (Termux) - - Make sure you have [Termux](https://f-droid.org/repo/com.termux_118.apk) installed. - - Download the binary with this command: +To install Nuru on your Android device using Termux, follow these steps: -``` -curl -O -L https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_android_arm64_v0.1.0.tar.gz -``` - - Extract the file: +1. **Ensure Termux is installed**: -``` -tar -xzvf nuru_android_arm64_v0.1.0.tar.gz -``` - - Add it to path: + - You can download and install [Termux](https://f-droid.org/en/packages/com.termux/). -``` -echo "alias nuru='~/nuru'" >> .bashrc -``` - - Confirm installation with: +2. **Create the target directory**: -``` -nuru -v + ```bash + mkdir -p /data/data/com.termux/files/usr/share/nuru + ``` + +3. **Download the Nuru package**: + + ```bash + curl -O -L https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Android_arm64.tar.gz + ``` + +4. **Extract the files to the target directory**: + + ```bash + tar -xzvf nuru_Android_arm64.tar.gz -C /data/data/com.termux/files/usr/share/nuru + ``` + +5. **Set up an alias for easy access**: + + ```bash + echo "alias nuru='/data/data/com.termux/files/usr/share/nuru/nuru'" >> ~/.bashrc + ``` + +6. **Reload the .bashrc file to apply the alias**: + + ```bash + source ~/.bashrc + ``` + +7. **Verify the installation**: + ```bash + nuru -v + ``` + +For a more streamlined installation, you can use the following one-liner: + +```bash +curl -O -L https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Android_arm64.tar.gz && mkdir -p /data/data/com.termux/files/usr/share/nuru && tar -xzvf nuru_Android_arm64.tar.gz -C /data/data/com.termux/files/usr/share/nuru && echo "alias nuru='/data/data/com.termux/files/usr/share/nuru/nuru'" >> ~/.bashrc && source ~/.bashrc && echo "Installation complete.." ``` ### Windows - - Make a bin directory if it doesn't exist: +- Executable: -``` -mkdir C:\bin -``` - - Download the Nuru Program [Here](https://github.com/AvicennaJr/Nuru/releases/download/v0.1.0/nuru_windows_amd64_v0.1.0.exe) - - Rename the downloaded program from `nuru_windows_amd64_v0.1.0.exe` to `nuru.exe` - - Move the file `nuru.exe` to the folder `C:\bin` - - Add the bin folder to Path with this command: + - Download the Nuru zip file [Here](https://github.com/NuruProgramming/Nuru/releases/download/v0.5.18/nuru_Windows_amd64.zip) + - Unzip to get the executable + - Double click the executable -``` -setx PATH "C:\bin;%PATH%" -``` - - Confirm installation with: +- Nuru Installer + > Coming Soon + +### Building From Source + +- Make sure you have golang installed (atleast 1.19.0 and above) +- Run the following command: ``` -nuru -v +go build -o nuru . ``` -### Building From Source - - - Make sure you have golang installed - - Run the following command: +- Copy nuru binary to path destination ~/go/bin ``` -go build -o nuru main.go +cp nuru ~/go/bin ``` - - You can optionally add the binary to $PATH as shown above - - Confirm installtion with: + +- Confirm installtion with: ``` nuru -v ``` -## Syntax +## Syntax At A Glance + +**NOTE** + +> There is a more detailed documentation of the language [here](https://nuruprogramming.org). Nuru, although still in its early stage, intends to be a fully functional programming language, and thus it has been baked with many features. ### Defining A Variable -To initiliaze a variable use the `acha` keyword: +You can define variables like this: ``` -acha x = 2; -acha y = 3; +x = 2; +y = 3; andika(x*y) // output is 6 ``` -You can reassign values to the variable after it has been initiliazed: -``` -x = 10 +You can also use the `fanya` keyword to define a variabe: -andika(x*y) // output is 30 ``` -**Note that Semicolons ";" are OPTIONAL** +fanya x = 3 +``` + +**Note that `fanya` keyword is OPTIONAL** ### Comments @@ -129,18 +188,15 @@ Nuru supports both single line and multiple line comments as shown below: // Single line comment /* - Multiple Line -Comment -*/ +Comment +*/ ``` ### Arithmetic Operations -For now Nuru supports `+`, `-`, `/` and `*`. More will be added. The `/` operation will truncate (round to a whole number) as Floating points are not supported yet. - -Nuru also provides precedence of operations using the BODMAS rule: +For now Nuru supports `+`, `-`, `/`, `*` and `%`. Nuru also provides precedence of operations using the BODMAS rule: ``` 2 + 2 * 3 // output = 8 @@ -152,20 +208,22 @@ Nuru also provides precedence of operations using the BODMAS rule: Nuru has the following types: -Type | Syntax | Comments ---------- | ----------------------------------------- | ----------------------- -BOOL | `kweli sikweli` | kweli == true, sikweli == false -INT | `1, 100, 342, -4` | These are signed 64 bit integers -STRING | `"" "mambo" "habari yako"` | They MUST be in DOUBLE QUOTES `"` -ARRAY | `[] [1, 2, 3] [1, "moja", kweli]` | Arrays can hold any types -DICT | `{} {"a": 3, 1: "moja", kweli: 2}` | Keys can be int, string or bool. Values can be anything +| Type | Syntax | Comments | +| ------ | ---------------------------------- | ------------------------------------------------------- | +| BOOL | `kweli sikweli` | kweli == true, sikweli == false | +| INT | `1, 100, 342, -4` | These are signed 64 bit integers | +| FLOAT | `2.3, 4.5. 100.8094` | Signed 64 bit floats | +| STRING | `"" "mambo" "habari yako"` | They can be in double `"` or single `'` quotes | +| ARRAY | `[] [1, 2, 3] [1, "moja", kweli]` | Arrays can hold any types | +| DICT | `{} {"a": 3, 1: "moja", kweli: 2}` | Keys can be int, string or bool. Values can be anything | +| NULL | `tupu` | These are nil objects | ### Functions This is how you define a function in Nuru: ``` -acha jumlisha = fn(x, y) { +jumlisha = unda(x, y) { rudisha x + y } @@ -175,7 +233,7 @@ andika(jumlisha(3,4)) Nuru also supports recursion: ``` -acha fibo = fn(x) { +fibo = unda(x) { kama (x == 0) { rudisha 0; } au kama (x == 1) { @@ -205,19 +263,20 @@ kama (2<1) { Nuru's while loop syntax is as follows: ``` -acha i = 10 +i = 10 wakati (i > 0) { andika(i) - i = i - 1 + i-- } ``` ### Arrays This is how you initiliaze and perform other array operations in Nuru: + ``` -acha arr = [] +arr = [] // To add elements @@ -225,9 +284,9 @@ sukuma(arr, 2) andika(arr) // output = [2] // Add two Arrays -acha arr2 = [1,2,3,4] +arr2 = [1,2,3,4] -acha arr3 = arr1 + arr2 +arr3 = arr1 + arr2 andika(arr3) // output = [2,1,2,3,4] @@ -244,11 +303,12 @@ andika(arr[3]) // output = 3 ### Dictionaries -Nuru also supports dictionaris and you can do a lot with them as follows: +Nuru also supports dictionaries and you can do a lot with them as follows: + ``` -acha mtu = {"jina": "Mojo", "kabila": "Mnyakusa"} +mtu = {"jina": "Mojo", "kabila": "Mnyakusa"} -// get value from key +// get value from key andika(mtu["jina"]) // output = Mojo andika(mtu["kabila"]); // output = Mnyakusa @@ -267,18 +327,37 @@ andika(mtu) // output = {"jina": "Avicenna", "kabila": "Mnyakusa", "anapoishi": // You can also add two Dictionaries -acha kazi = {"kazi": "jambazi"} +kazi = {"kazi": "jambazi"} mtu = mtu + kazi andika(mtu) // output = {"jina": "Avicenna", "kabila": "Mnyakusa", "anapoishi": "Dar Es Salaam", "kazi": "jambazi"} ``` +### For Loops + +These can iterate over strings, arrays and dictionaries: + +``` +kwa i ktk "habari" { + andika(i) +} +/* //output +h +a +b +a +r +i +*/ +``` + ### Getting Input From User In Nuru you can get input from users using the `jaza()` keyword as follows: + ``` -acha jina = jaza("Unaitwa nani? ") // will prompt for input +jina = jaza("Unaitwa nani? ") // will prompt for input andika("Habari yako " + jina) ``` @@ -288,6 +367,7 @@ andika("Habari yako " + jina) ### Using The Intepreter: You can enter the intepreter by simply running the `nuru` command: + ``` nuru >>> andika("karibu") @@ -295,13 +375,16 @@ karibu >>> 2 + 2 4 ``` + Kindly Note that everything should be placed in a single line. Here's an example: + ``` >>> kama (x > y) {andika("X ni kubwa")} sivyo {andika("Y ni kubwa")} ``` + ### Running From File -To run a Nuru script, write the `nuru` command followed by the name of the file with a `.nr` extension: +To run a Nuru script, write the `nuru` command followed by the name of the file with a `.nr` or `.sw` extension: ``` nuru myFile.nr @@ -309,11 +392,23 @@ nuru myFile.nr ## Issues -Kindly open an [Issue](https://github.com/AvicennaJr/Nuru/issues) to make suggestions and anything else. +Kindly open an [Issue](https://github.com/NuruProgramming/Nuru/issues) to make suggestions and anything else. ## Contributions -All contributions are welcomed. Clone the repo, hack it, make sure all tests are passing then submit a pull request. +### Documentation + +There are documentations for two languages, English and Kiswahili, which are both under the `docs` folder. All files are written in markdown. Feel free to contribute by making a pull request. + +### Code + +Clone the repo, hack it, make sure all tests are passing then submit a pull request. + +> Make sure ALL tests are passing before making a pull request. You can confirm with running `make tests` + +## Community + +Nuru has a passionate community, join us on [Telegram](https://t.me/NuruProgrammingChat) ## License diff --git a/assets/Nuru.png b/assets/Nuru.png new file mode 100644 index 0000000..e41cbab Binary files /dev/null and b/assets/Nuru.png differ diff --git a/assets/Nuru.svg b/assets/Nuru.svg new file mode 100644 index 0000000..4b67102 --- /dev/null +++ b/assets/Nuru.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + diff --git a/assets/nuru-logo (Transparent).png b/assets/nuru-logo (Transparent).png new file mode 100644 index 0000000..935a003 Binary files /dev/null and b/assets/nuru-logo (Transparent).png differ diff --git a/ast/ast.go b/ast/ast.go index 241a169..c42241f 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -4,12 +4,12 @@ import ( "bytes" "strings" - "github.com/AvicennaJr/Nuru/token" + "github.com/NuruProgramming/Nuru/token" ) type Node interface { TokenLiteral() string - String() string // to help debug the many errors lmao + String() string } type Statement interface { @@ -179,13 +179,13 @@ func (ie *IfExpression) expressionNode() {} func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *IfExpression) String() string { var out bytes.Buffer - out.WriteString("if") + out.WriteString("kama") out.WriteString(ie.Condition.String()) out.WriteString(" ") out.WriteString(ie.Consequence.String()) if ie.Alternative != nil { - out.WriteString("else") + out.WriteString("sivyo") out.WriteString(ie.Alternative.String()) } @@ -211,7 +211,9 @@ func (bs *BlockStatement) String() string { type FunctionLiteral struct { Token token.Token + Name string Parameters []*Identifier + Defaults map[string]Expression Body *BlockStatement } @@ -331,6 +333,42 @@ func (dl *DictLiteral) String() string { return out.String() } +type Assign struct { + Token token.Token + Name *Identifier + Value Expression +} + +func (ae *Assign) expressionNode() {} +func (ae *Assign) TokenLiteral() string { return ae.Token.Literal } +func (ae *Assign) String() string { + var out bytes.Buffer + + out.WriteString(ae.Name.String()) + out.WriteString(ae.TokenLiteral()) + out.WriteString(ae.Value.String()) + + return out.String() +} + +type AssignEqual struct { + Token token.Token + Left *Identifier + Value Expression +} + +func (ae *AssignEqual) expressionNode() {} +func (ae *AssignEqual) TokenLiteral() string { return ae.Token.Literal } +func (ae *AssignEqual) String() string { + var out bytes.Buffer + + out.WriteString(ae.Left.String()) + out.WriteString(ae.TokenLiteral()) + out.WriteString(ae.Value.String()) + + return out.String() +} + type AssignmentExpression struct { Token token.Token Left Expression @@ -360,10 +398,250 @@ func (we *WhileExpression) TokenLiteral() string { return we.Token.Literal } func (we *WhileExpression) String() string { var out bytes.Buffer - out.WriteString("while") + out.WriteString("wakati") out.WriteString(we.Condition.String()) out.WriteString(" ") out.WriteString(we.Consequence.String()) return out.String() } + +type Null struct { + Token token.Token +} + +func (n *Null) expressionNode() {} +func (n *Null) TokenLiteral() string { return n.Token.Literal } +func (n *Null) String() string { return n.Token.Literal } + +type Break struct { + Statement + Token token.Token // the 'break' token +} + +func (b *Break) expressionNode() {} +func (b *Break) TokenLiteral() string { return b.Token.Literal } +func (b *Break) String() string { return b.Token.Literal } + +type Continue struct { + Statement + Token token.Token // the 'continue' token +} + +func (c *Continue) expressionNode() {} +func (c *Continue) TokenLiteral() string { return c.Token.Literal } +func (c *Continue) String() string { return c.Token.Literal } + +type PostfixExpression struct { + Token token.Token + Operator string +} + +func (pe *PostfixExpression) expressionNode() {} +func (pe *PostfixExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PostfixExpression) String() string { + var out bytes.Buffer + out.WriteString("(") + out.WriteString(pe.Token.Literal) + out.WriteString(pe.Operator) + out.WriteString(")") + return out.String() +} + +type FloatLiteral struct { + Token token.Token + Value float64 +} + +func (fl *FloatLiteral) expressionNode() {} +func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FloatLiteral) String() string { return fl.Token.Literal } + +type For struct { + Token token.Token + Identifier string // "i" + StarterName *Identifier // i = 0 + StarterValue Expression + Closer Expression // i++ + Condition Expression // i < 1 + Block *BlockStatement +} + +type ForIn struct { + Token token.Token + Key string + Value string + Iterable Expression + Block *BlockStatement +} + +func (fi *ForIn) expressionNode() {} +func (fi *ForIn) TokenLiteral() string { return fi.Token.Literal } +func (fi *ForIn) String() string { + var out bytes.Buffer + + out.WriteString("kwa ") + if fi.Key != "" { + out.WriteString(fi.Key + ", ") + } + out.WriteString(fi.Value + " ") + out.WriteString("ktk ") + out.WriteString(fi.Iterable.String() + " {\n") + out.WriteString("\t" + fi.Block.String()) + out.WriteString("\n}") + + return out.String() +} + +type CaseExpression struct { + Token token.Token + Default bool + Expr []Expression + Block *BlockStatement +} + +func (ce *CaseExpression) expressionNode() {} +func (ce *CaseExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CaseExpression) String() string { + var out bytes.Buffer + + if ce.Default { + out.WriteString("kawaida ") + } else { + out.WriteString("ikiwa ") + + tmp := []string{} + for _, exp := range ce.Expr { + tmp = append(tmp, exp.String()) + } + out.WriteString(strings.Join(tmp, ",")) + } + out.WriteString(ce.Block.String()) + return out.String() +} + +type SwitchExpression struct { + Token token.Token + Value Expression + Choices []*CaseExpression +} + +func (se *SwitchExpression) expressionNode() {} +func (se *SwitchExpression) TokenLiteral() string { return se.Token.Literal } +func (se *SwitchExpression) String() string { + var out bytes.Buffer + out.WriteString("\nbadili (") + out.WriteString(se.Value.String()) + out.WriteString(")\n{\n") + + for _, tmp := range se.Choices { + if tmp != nil { + out.WriteString(tmp.String()) + } + } + out.WriteString("}\n") + + return out.String() +} + +type MethodExpression struct { + Token token.Token + Object Expression + Method Expression + Arguments []Expression + Defaults map[string]Expression +} + +func (me *MethodExpression) expressionNode() {} +func (me *MethodExpression) TokenLiteral() string { return me.Token.Literal } +func (me *MethodExpression) String() string { + var out bytes.Buffer + out.WriteString(me.Object.String()) + out.WriteString(".") + out.WriteString(me.Method.String()) + + return out.String() +} + +type Import struct { + Token token.Token + Identifiers map[string]*Identifier +} + +func (i *Import) expressionNode() {} +func (i *Import) TokenLiteral() string { return i.Token.Literal } +func (i *Import) String() string { + var out bytes.Buffer + out.WriteString("tumia ") + for k := range i.Identifiers { + out.WriteString(k + " ") + } + return out.String() +} + +type PackageBlock struct { + Token token.Token + Statements []Statement +} + +func (pb *PackageBlock) statementNode() {} +func (pb *PackageBlock) TokenLiteral() string { return pb.Token.Literal } +func (pb *PackageBlock) String() string { + var out bytes.Buffer + + for _, s := range pb.Statements { + out.WriteString(s.String()) + } + + return out.String() +} + +type Package struct { + Token token.Token + Name *Identifier + Block *BlockStatement +} + +func (p *Package) expressionNode() {} +func (p *Package) TokenLiteral() string { return p.Token.Literal } +func (p *Package) String() string { + var out bytes.Buffer + + out.WriteString("pakeji " + p.Name.Value + "\n") + out.WriteString("::\n") + for _, s := range p.Block.Statements { + out.WriteString(s.String()) + } + out.WriteString("\n::") + + return out.String() +} + +type At struct { + Token token.Token +} + +func (a *At) expressionNode() {} +func (a *At) TokenLiteral() string { return a.Token.Literal } +func (a *At) String() string { return "@" } + +type PropertyAssignment struct { + Token token.Token // the '=' token + Name *PropertyExpression + Value Expression +} + +func (pa *PropertyAssignment) expressionNode() {} +func (pa *PropertyAssignment) TokenLiteral() string { return pa.Token.Literal } +func (pa *PropertyAssignment) String() string { return "Ngl I'm tired" } + +type PropertyExpression struct { + Expression + Token token.Token // The . token + Object Expression + Property Expression +} + +func (pe *PropertyExpression) expressionNode() {} +func (pe *PropertyExpression) TokenLiteral() string { return pe.Token.Literal } +func (pe *PropertyExpression) String() string { return "Ngl I'm tired part two" } diff --git a/ast/ast_test.go b/ast/ast_test.go index be9d6e3..f9eba0d 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -3,14 +3,14 @@ package ast import ( "testing" - "github.com/AvicennaJr/Nuru/token" + "github.com/NuruProgramming/Nuru/token" ) func TestString(t *testing.T) { program := &Program{ Statements: []Statement{ &LetStatement{ - Token: token.Token{Type: token.LET, Literal: "acha"}, + Token: token.Token{Type: token.LET, Literal: "fanya"}, Name: &Identifier{ Token: token.Token{Type: token.IDENT, Literal: "myVar"}, Value: "myVar", @@ -23,7 +23,7 @@ func TestString(t *testing.T) { }, } - if program.String() != "acha myVar = anotherVar;" { + if program.String() != "fanya myVar = anotherVar;" { t.Errorf("program.String() wrong. got=%q", program.String()) } } diff --git a/evaluator/assign.go b/evaluator/assign.go new file mode 100644 index 0000000..f867321 --- /dev/null +++ b/evaluator/assign.go @@ -0,0 +1,16 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalAssign(node *ast.Assign, env *object.Environment) object.Object { + val := Eval(node.Value, env) + if isError(val) { + return val + } + + obj := env.Set(node.Name.Value, val) + return obj +} diff --git a/evaluator/assignEqual.go b/evaluator/assignEqual.go new file mode 100644 index 0000000..6ec2bf2 --- /dev/null +++ b/evaluator/assignEqual.go @@ -0,0 +1,152 @@ +package evaluator + +import ( + "strings" + + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalAssignEqual(node *ast.AssignEqual, env *object.Environment) object.Object { + left := Eval(node.Left, env) + if isError(left) { + return left + } + + value := Eval(node.Value, env) + if isError(value) { + return value + } + + switch node.Token.Literal { + case "+=": + switch arg := left.(type) { + case *object.Integer: + switch val := value.(type) { + case *object.Integer: + v := arg.Value + val.Value + return env.Set(node.Left.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := float64(arg.Value) + val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '+=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.Float: + switch val := value.(type) { + case *object.Integer: + v := arg.Value + float64(val.Value) + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + case *object.Float: + v := arg.Value + val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '+=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.String: + switch val := value.(type) { + case *object.String: + v := arg.Value + val.Value + return env.Set(node.Left.Token.Literal, &object.String{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '+=' kwa %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + default: + return newError("Mstari %d: Huwezi kutumia '+=' na %v", node.Token.Line, arg.Type()) + } + case "-=": + switch arg := left.(type) { + case *object.Integer: + switch val := value.(type) { + case *object.Integer: + v := arg.Value - val.Value + return env.Set(node.Left.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := float64(arg.Value) - val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '-=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.Float: + switch val := value.(type) { + case *object.Integer: + v := arg.Value - float64(val.Value) + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + case *object.Float: + v := arg.Value - val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '-=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + default: + return newError("Mstari %d: Huwezi kutumia '-=' na %v", node.Token.Line, arg.Type()) + } + case "*=": + switch arg := left.(type) { + case *object.Integer: + switch val := value.(type) { + case *object.Integer: + v := arg.Value * val.Value + return env.Set(node.Left.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := float64(arg.Value) * val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + case *object.String: + v := strings.Repeat(val.Value, int(arg.Value)) + return env.Set(node.Left.Token.Literal, &object.String{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '*=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.Float: + switch val := value.(type) { + case *object.Integer: + v := arg.Value * float64(val.Value) + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + case *object.Float: + v := arg.Value * val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '*=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.String: + switch val := value.(type) { + case *object.Integer: + v := strings.Repeat(arg.Value, int(val.Value)) + return env.Set(node.Left.Token.Literal, &object.String{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '+=' kwa %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + default: + return newError("Mstari %d: Huwezi kutumia '*=' na %v", node.Token.Line, arg.Type()) + } + case "/=": + switch arg := left.(type) { + case *object.Integer: + switch val := value.(type) { + case *object.Integer: + v := arg.Value / val.Value + return env.Set(node.Left.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := float64(arg.Value) / val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '/=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + case *object.Float: + switch val := value.(type) { + case *object.Integer: + v := arg.Value / float64(val.Value) + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + case *object.Float: + v := arg.Value / val.Value + return env.Set(node.Left.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: Huwezi kutumia '/=' kujumlisha %v na %v", node.Token.Line, arg.Type(), val.Type()) + } + default: + return newError("Mstari %d: Huwezi kutumia '/=' na %v", node.Token.Line, arg.Type()) + } + default: + return newError("Mstari %d: Operesheni Haifahamiki %s", node.Token.Line, node.Token.Literal) + } +} diff --git a/evaluator/at.go b/evaluator/at.go new file mode 100644 index 0000000..6cdb35f --- /dev/null +++ b/evaluator/at.go @@ -0,0 +1,13 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalAt(node *ast.At, env *object.Environment) object.Object { + if at, ok := env.Get("@"); ok { + return at + } + return newError("Iko nje ya scope") +} diff --git a/evaluator/bang.go b/evaluator/bang.go new file mode 100644 index 0000000..ee7d6fb --- /dev/null +++ b/evaluator/bang.go @@ -0,0 +1,16 @@ +package evaluator + +import "github.com/NuruProgramming/Nuru/object" + +func evalBangOperatorExpression(right object.Object) object.Object { + switch right { + case TRUE: + return FALSE + case FALSE: + return TRUE + case NULL: + return TRUE + default: + return FALSE + } +} diff --git a/evaluator/block.go b/evaluator/block.go new file mode 100644 index 0000000..3e27ef5 --- /dev/null +++ b/evaluator/block.go @@ -0,0 +1,23 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { + var result object.Object + + for _, statement := range block.Statements { + result = Eval(statement, env) + + if result != nil { + rt := result.Type() + if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ || rt == object.CONTINUE_OBJ || rt == object.BREAK_OBJ { + return result + } + } + } + + return result +} diff --git a/evaluator/builtins.go b/evaluator/builtins.go index f07138c..d35f1e9 100644 --- a/evaluator/builtins.go +++ b/evaluator/builtins.go @@ -5,69 +5,17 @@ import ( "fmt" "io" "os" + "strings" - "github.com/AvicennaJr/Nuru/object" + "github.com/NuruProgramming/Nuru/object" ) var builtins = map[string]*object.Builtin{ - "idadi": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("Hoja hazilingani, tunahitaji=1, tumepewa=%d", len(args)) - } - - switch arg := args[0].(type) { - case *object.Array: - return &object.Integer{Value: int64(len(arg.Elements))} - case *object.String: - return &object.Integer{Value: int64(len(arg.Value))} - default: - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - }, - }, - "yamwisho": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 1 { - return newError("Samahani, tunahitaji Hoja moja tu, wewe umeweka %d", len(args)) - } - if args[0].Type() != object.ARRAY_OBJ { - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - - arr := args[0].(*object.Array) - length := len(arr.Elements) - if length > 0 { - return arr.Elements[length-1] - } - - return NULL - }, - }, - "sukuma": { - Fn: func(args ...object.Object) object.Object { - if len(args) != 2 { - return newError("Samahani, tunahitaji Hoja 2, wewe umeweka %d", len(args)) - } - if args[0].Type() != object.ARRAY_OBJ { - return newError("Samahani, hii function haitumiki na %s", args[0].Type()) - } - - arr := args[0].(*object.Array) - length := len(arr.Elements) - - newElements := make([]object.Object, length+1) - copy(newElements, arr.Elements) - newElements[length] = args[1] - - return &object.Array{Elements: newElements} - }, - }, "jaza": { Fn: func(args ...object.Object) object.Object { if len(args) > 1 { - return newError("Samahani, hii function inapokea hoja 0 au 1, wewe umeweka %d", len(args)) + return newError("Samahani, kiendesha hiki kinapokea hoja 0 au 1, wewe umeweka %d", len(args)) } if len(args) > 0 && args[0].Type() != object.STRING_OBJ { @@ -93,12 +41,206 @@ var builtins = map[string]*object.Builtin{ if len(args) == 0 { fmt.Println("") } else { + var arr []string for _, arg := range args { - - fmt.Println(arg.Inspect()) + if arg == nil { + return newError("Hauwezi kufanya operesheni hii") + } + arr = append(arr, arg.Inspect()) } + str := strings.Join(arr, " ") + fmt.Println(str) } return nil }, }, + "_andika": { + Fn: func(args ...object.Object) object.Object { + if len(args) == 0 { + return &object.String{Value: "\n"} + } else { + var arr []string + for _, arg := range args { + if arg == nil { + return newError("Hauwezi kufanya operesheni hii") + } + arr = append(arr, arg.Inspect()) + } + str := strings.Join(arr, " ") + return &object.String{Value: str} + } + }, + }, + "aina": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji hoja 1, wewe umeweka %d", len(args)) + } + + return &object.String{Value: string(args[0].Type())} + }, + }, + "fungua": { + Fn: func(args ...object.Object) object.Object { + + if len(args) != 1 { + return newError("Samahani, tunahitaji hoja 1, wewe umeweka %d", len(args)) + } + filename := args[0].(*object.String).Value + + file, err := os.ReadFile(filename) + if err != nil { + return &object.Error{Message: "Tumeshindwa kusoma faili au faili halipo"} + } + return &object.File{Filename: filename, Content: string(file)} + }, + }, + "mfululizo": { + Fn: func(args ...object.Object) object.Object { + if len(args) < 1 || len(args) > 3 { + return newError("Samahani, mfululizo inahitaji hoja 1 hadi 3, wewe umeweka %d", len(args)) + } + + var start, end, step int64 + var err error + + switch len(args) { + case 1: + end, err = getIntValue(args[0]) + if err != nil { + return newError("Hoja lazima iwe nambari nzima") + } + start, step = 0, 1 + case 2: + start, err = getIntValue(args[0]) + if err != nil { + return newError("Hoja ya kwanza lazima iwe nambari nzima") + } + end, err = getIntValue(args[1]) + if err != nil { + return newError("Hoja ya pili lazima iwe nambari nzima") + } + step = 1 + case 3: + start, err = getIntValue(args[0]) + if err != nil { + return newError("Hoja ya kwanza lazima iwe nambari nzima") + } + end, err = getIntValue(args[1]) + if err != nil { + return newError("Hoja ya pili lazima iwe nambari nzima") + } + step, err = getIntValue(args[2]) + if err != nil { + return newError("Hoja ya tatu lazima iwe nambari nzima") + } + if step == 0 { + return newError("Hatua haiwezi kuwa sifuri") + } + } + + elements := []object.Object{} + for i := start; (step > 0 && i < end) || (step < 0 && i > end); i += step { + elements = append(elements, &object.Integer{Value: i}) + } + + return &object.Array{Elements: elements} + }, + }, + + "badilisha": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 2 { + return newError("Samahani, badili inahitaji hoja 2, wewe umeweka %d", len(args)) + } + + value := args[0] + targetType := args[1] + + if targetType.Type() != object.STRING_OBJ { + return newError("Aina ya lengo lazima iwe neno") + } + + targetTypeStr := targetType.(*object.String).Value + + switch targetTypeStr { + case "NAMBA": + return convertToInteger(value) + case "DESIMALI": + return convertToFloat(value) + case "NENO": + return convertToString(value) + case "BOOLEAN": + return convertToBoolean(value) + default: + return newError("Aina isiyojulikana: %s", targetTypeStr) + } + }, + }, + "namba": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, namba inahitaji hoja 1, wewe umeweka %d", len(args)) + } + value := args[0] + return convertToInteger(value) + }, + }, + "tungo": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("Samahani, tungo inahitaji hoja 1, wewe umeweka %d", len(args)) + } + value := args[0] + return convertToString(value) + }, + }, + + // "jumla": { + // Fn: func(args ...object.Object) object.Object { + // if len(args) != 1 { + // return newError("Hoja hazilingani, tunahitaji=1, tumepewa=%d", len(args)) + // } + + // switch arg := args[0].(type) { + // case *object.Array: + + // var sums float64 + // for _, num := range arg.Elements { + + // if num.Type() != object.INTEGER_OBJ && num.Type() != object.FLOAT_OBJ { + // return newError("Samahani namba tu zinahitajika") + // } else { + // if num.Type() == object.INTEGER_OBJ { + // no, _ := strconv.Atoi(num.Inspect()) + // floatnum := float64(no) + // sums += floatnum + // } else if num.Type() == object.FLOAT_OBJ { + // no, _ := strconv.ParseFloat(num.Inspect(), 64) + // sums += no + // } + + // } + // } + + // if math.Mod(sums, 1) == 0 { + // return &object.Integer{Value: int64(sums)} + // } + + // return &object.Float{Value: float64(sums)} + + // default: + // return newError("Samahani, hii function haitumiki na %s", args[0].Type()) + // } + // }, + // }, +} + +func getIntValue(obj object.Object) (int64, error) { + switch obj := obj.(type) { + case *object.Integer: + return obj.Value, nil + default: + return 0, fmt.Errorf("expected integer, got %T", obj) + } } diff --git a/evaluator/call.go b/evaluator/call.go new file mode 100644 index 0000000..b936afe --- /dev/null +++ b/evaluator/call.go @@ -0,0 +1,97 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalCall(node *ast.CallExpression, env *object.Environment) object.Object { + function := Eval(node.Function, env) + + if isError(function) { + return function + } + + var args []object.Object + + switch fn := function.(type) { + case *object.Function: + args = evalArgsExpressions(node, fn, env) + case *object.Package: + obj, ok := fn.Scope.Get("andaa") + if !ok { + return newError("Pakeji haina 'andaa'") + } + args = evalArgsExpressions(node, obj.(*object.Function), env) + default: + args = evalExpressions(node.Arguments, env) + } + + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + return applyFunction(function, args, node.Token.Line) +} + +func evalArgsExpressions(node *ast.CallExpression, fn *object.Function, env *object.Environment) []object.Object { + argsList := &object.Array{} + argsHash := &object.Dict{} + argsHash.Pairs = make(map[object.HashKey]object.DictPair) + for _, exprr := range node.Arguments { + switch exp := exprr.(type) { + case *ast.Assign: + val := Eval(exp.Value, env) + if isError(val) { + return []object.Object{val} + } + var keyHash object.HashKey + key := &object.String{Value: exp.Name.Value} + keyHash = key.HashKey() + pair := object.DictPair{Key: key, Value: val} + argsHash.Pairs[keyHash] = pair + default: + evaluated := Eval(exp, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + argsList.Elements = append(argsList.Elements, evaluated) + } + } + + var result []object.Object + var params = map[string]bool{} + for _, exp := range fn.Parameters { + params[exp.Value] = true + if len(argsList.Elements) > 0 { + result = append(result, argsList.Elements[0]) + argsList.Elements = argsList.Elements[1:] + } else { + keyParam := &object.String{Value: exp.Value} + keyParamHash := keyParam.HashKey() + if valParam, ok := argsHash.Pairs[keyParamHash]; ok { + result = append(result, valParam.Value) + delete(argsHash.Pairs, keyParamHash) + } else { + if _e, _ok := fn.Defaults[exp.Value]; _ok { + evaluated := Eval(_e, env) + if isError(evaluated) { + return []object.Object{evaluated} + } + result = append(result, evaluated) + } else { + return []object.Object{&object.Error{Message: "Tumekosa Hoja"}} + } + } + } + } + + for _, pair := range argsHash.Pairs { + if _, ok := params[pair.Key.(*object.String).Value]; ok { + return []object.Object{&object.Error{Message: "Tumepewa hoja nyingi kwa parameter moja"}} + } + } + + return result + +} diff --git a/evaluator/dict.go b/evaluator/dict.go new file mode 100644 index 0000000..2d417ca --- /dev/null +++ b/evaluator/dict.go @@ -0,0 +1,32 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { + pairs := make(map[object.HashKey]object.DictPair) + + for keyNode, valueNode := range node.Pairs { + key := Eval(keyNode, env) + if isError(key) { + return key + } + + hashKey, ok := key.(object.Hashable) + if !ok { + return newError("Mstari %d: Hashing imeshindikana: %s", node.Token.Line, key.Type()) + } + + value := Eval(valueNode, env) + if isError(value) { + return value + } + + hashed := hashKey.HashKey() + pairs[hashed] = object.DictPair{Key: key, Value: value} + } + + return &object.Dict{Pairs: pairs} +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 9b2726b..5fd219d 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -2,16 +2,17 @@ package evaluator import ( "fmt" - "strings" - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/object" + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" ) var ( - NULL = &object.Null{} - TRUE = &object.Boolean{Value: true} - FALSE = &object.Boolean{Value: false} + NULL = &object.Null{} + TRUE = &object.Boolean{Value: true} + FALSE = &object.Boolean{Value: false} + BREAK = &object.Break{} + CONTINUE = &object.Continue{} ) func Eval(node ast.Node, env *object.Environment) object.Object { @@ -25,6 +26,9 @@ func Eval(node ast.Node, env *object.Environment) object.Object { case *ast.IntegerLiteral: return &object.Integer{Value: node.Value} + case *ast.FloatLiteral: + return &object.Float{Value: node.Value} + case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) @@ -33,7 +37,7 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(right) { return right } - return evalPrefixExpression(node.Operator, right) + return evalPrefixExpression(node.Operator, right, node.Token.Line) case *ast.InfixExpression: left := Eval(node.Left, env) @@ -41,10 +45,12 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return left } right := Eval(node.Right, env) - if isError(right) { + if isError(right) && right != nil { return right } - return evalInfixExpression(node.Operator, left, right) + return evalInfixExpression(node.Operator, left, right, node.Token.Line) + case *ast.PostfixExpression: + return evalPostfixExpression(env, node.Operator, node) case *ast.BlockStatement: return evalBlockStatement(node, env) @@ -71,23 +77,21 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return evalIdentifier(node, env) case *ast.FunctionLiteral: - params := node.Parameters - body := node.Body - return &object.Function{Parameters: params, Env: env, Body: body} + return evalFunction(node, env) + + case *ast.MethodExpression: + return evalMethodExpression(node, env) + + case *ast.Import: + return evalImport(node, env) case *ast.CallExpression: - function := Eval(node.Function, env) - if isError(function) { - return function - } - args := evalExpressions(node.Arguments, env) - if len(args) == 1 && isError(args[0]) { - return args[0] - } - return applyFunction(function, args) + return evalCall(node, env) + case *ast.StringLiteral: return &object.String{Value: node.Value} - + case *ast.At: + return evalAt(node, env) case *ast.ArrayLiteral: elements := evalExpressions(node.Elements, env) if len(elements) == 1 && isError(elements[0]) { @@ -103,11 +107,38 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if isError(index) { return index } - return evalIndexExpression(left, index) + return evalIndexExpression(left, index, node.Token.Line) case *ast.DictLiteral: return evalDictLiteral(node, env) case *ast.WhileExpression: return evalWhileExpression(node, env) + case *ast.Break: + return evalBreak(node) + case *ast.Continue: + return evalContinue(node) + case *ast.SwitchExpression: + return evalSwitchStatement(node, env) + case *ast.Null: + return NULL + // case *ast.For: + // return evalForExpression(node, env) + case *ast.ForIn: + return evalForInExpression(node, env, node.Token.Line) + case *ast.Package: + return evalPackage(node, env) + case *ast.PropertyExpression: + return evalPropertyExpression(node, env) + case *ast.PropertyAssignment: + val := Eval(node.Value, env) + if isError(val) { + return val + } + return evalPropertyAssignment(node.Name, val, env) + case *ast.Assign: + return evalAssign(node, env) + case *ast.AssignEqual: + return evalAssignEqual(node, env) + case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { @@ -119,6 +150,17 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return value } + // This is an easy way to assign operators like +=, -= etc + // for index expressions (arrays and dicts) where applicable + op := node.Token.Literal + if len(op) >= 2 { + op = op[:len(op)-1] + value = evalInfixExpression(op, left, value, node.Token.Line) + if isError(value) { + return value + } + } + if ident, ok := node.Left.(*ast.Identifier); ok { env.Set(ident.Value, value) } else if ie, ok := node.Left.(*ast.IndexExpression); ok { @@ -133,12 +175,12 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return index } if idx, ok := index.(*object.Integer); ok { - if int(idx.Value) > len(array.Elements) { + if int(idx.Value) >= len(array.Elements) { return newError("Index imezidi idadi ya elements") } array.Elements[idx.Value] = value } else { - return newError("Hauwezi kufanya opereshen hii na %#v", index) + return newError("Hauwezi kufanya operesheni hii na %#v", index) } } else if hash, ok := obj.(*object.Dict); ok { key := Eval(ie.Index, env) @@ -149,13 +191,13 @@ func Eval(node ast.Node, env *object.Environment) object.Object { hashed := hashKey.HashKey() hash.Pairs[hashed] = object.DictPair{Key: key, Value: value} } else { - return newError("Hauwezi kufanya opereshen hii na %T", key) + return newError("Hauwezi kufanya operesheni hii na %T", key) } } else { - return newError("%T haifanyi operation hii", obj) + return newError("%T haifanyi operesheni hii", obj) } } else { - return newError("Tumia neno kama variable, sio %T", left) + return newError("Tumia neno kama kibadala, sio %T", left) } } @@ -166,8 +208,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object { func evalProgram(program *ast.Program, env *object.Environment) object.Object { var result object.Object - for _, statment := range program.Statements { - result = Eval(statment, env) + for _, statement := range program.Statements { + result = Eval(statement, env) switch result := result.(type) { case *object.ReturnValue: @@ -187,160 +229,6 @@ func nativeBoolToBooleanObject(input bool) *object.Boolean { return FALSE } -func evalPrefixExpression(operator string, right object.Object) object.Object { - switch operator { - case "!": - return evalBangOperatorExpression(right) - case "-": - return evalMinusPrefixOperatorExpression(right) - default: - return newError("operesheni haieleweki: %s%s", operator, right.Type()) - } -} - -func evalBangOperatorExpression(right object.Object) object.Object { - switch right { - case TRUE: - return FALSE - case FALSE: - return TRUE - case NULL: - return TRUE - default: - return FALSE - } -} - -func evalMinusPrefixOperatorExpression(right object.Object) object.Object { - if right.Type() != object.INTEGER_OBJ { - return newError("Operesheni Haielweki: -%s", right.Type()) - } - - value := right.(*object.Integer).Value - return &object.Integer{Value: -value} -} - -func evalInfixExpression( - operator string, - left, right object.Object, -) object.Object { - switch { - - case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: - leftVal := left.(*object.Dict).Pairs - rightVal := right.(*object.Dict).Pairs - pairs := make(map[object.HashKey]object.DictPair) - for k, v := range leftVal { - pairs[k] = v - } - for k, v := range rightVal { - pairs[k] = v - } - return &object.Dict{Pairs: pairs} - - case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := right.(*object.Array).Elements - elements := make([]object.Object, len(leftVal)+len(rightVal)) - elements = append(leftVal, rightVal...) - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.Array).Elements - rightVal := int(right.(*object.Integer).Value) - elements := leftVal - for i := rightVal; i > 1; i-- { - elements = append(elements, leftVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: - leftVal := int(left.(*object.Integer).Value) - rightVal := right.(*object.Array).Elements - elements := rightVal - for i := leftVal; i > 1; i-- { - elements = append(elements, rightVal...) - } - return &object.Array{Elements: elements} - - case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: - leftVal := left.(*object.String).Value - rightVal := right.(*object.Integer).Value - return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} - - case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: - leftVal := left.(*object.Integer).Value - rightVal := right.(*object.String).Value - return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} - - case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: - return evalIntegerInfixExpression(operator, left, right) - - case operator == "==": - return nativeBoolToBooleanObject(left == right) - - case operator == "!=": - return nativeBoolToBooleanObject(left != right) - - case left.Type() != right.Type(): - return newError("Aina Hazilingani: %s %s %s", - left.Type(), operator, right.Type()) - - case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: - return evalStringInfixExpression(operator, left, right) - - default: - return newError("Operesheni Haielweki: %s %s %s", - left.Type(), operator, right.Type()) - } -} - -func evalIntegerInfixExpression( - operator string, - left, right object.Object, -) object.Object { - leftVal := left.(*object.Integer).Value - rightVal := right.(*object.Integer).Value - - switch operator { - case "+": - return &object.Integer{Value: leftVal + rightVal} - case "-": - return &object.Integer{Value: leftVal - rightVal} - case "*": - return &object.Integer{Value: leftVal * rightVal} - case "/": - return &object.Integer{Value: leftVal / rightVal} - case "<": - return nativeBoolToBooleanObject(leftVal < rightVal) - case ">": - return nativeBoolToBooleanObject(leftVal > rightVal) - case "==": - return nativeBoolToBooleanObject(leftVal == rightVal) - case "!=": - return nativeBoolToBooleanObject(leftVal != rightVal) - default: - return newError("Operesheni Haielweki: %s %s %s", - left.Type(), operator, right.Type()) - } -} - -func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { - condition := Eval(ie.Condition, env) - - if isError(condition) { - return condition - } - - if isTruthy(condition) { - return Eval(ie.Consequence, env) - } else if ie.Alternative != nil { - return Eval(ie.Alternative, env) - } else { - return NULL - } -} - func isTruthy(obj object.Object) bool { switch obj { case NULL: @@ -354,25 +242,7 @@ func isTruthy(obj object.Object) bool { } } -func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) object.Object { - var result object.Object - - for _, statment := range block.Statements { - result = Eval(statment, env) - - if result != nil { - rt := result.Type() - if rt == object.RETURN_VALUE_OBJ || rt == object.ERROR_OBJ { - return result - } - } - } - - return result -} - func newError(format string, a ...interface{}) *object.Error { - format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) return &object.Error{Message: fmt.Sprintf(format, a...)} } @@ -384,17 +254,6 @@ func isError(obj object.Object) bool { return false } -func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { - if val, ok := env.Get(node.Value); ok { - return val - } - if builtin, ok := builtins[node.Value]; ok { - return builtin - } - - return newError("Neno Halifahamiki: " + node.Value) -} - func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { var result []object.Object @@ -410,16 +269,37 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob return result } -func applyFunction(fn object.Object, args []object.Object) object.Object { +func applyFunction(fn object.Object, args []object.Object, line int) object.Object { switch fn := fn.(type) { case *object.Function: extendedEnv := extendedFunctionEnv(fn, args) evaluated := Eval(fn.Body, extendedEnv) return unwrapReturnValue(evaluated) case *object.Builtin: - return fn.Fn(args...) + if result := fn.Fn(args...); result != nil { + return result + } + return NULL + case *object.Package: + obj := &object.Instance{ + Package: fn, + Env: object.NewEnclosedEnvironment(fn.Env), + } + obj.Env.Set("@", obj) + node, ok := fn.Scope.Get("andaa") + if !ok { + return newError("Hamna andaa kiendesha") + } + node.(*object.Function).Env.Set("@", obj) + applyFunction(node, args, fn.Name.Token.Line) + node.(*object.Function).Env.Del("@") + return obj default: - return newError("sio function: %s", fn.Type()) + if fn != nil { + return newError("Mstari %d: Hiki sio kiendesha: %s", line, fn.Type()) + } else { + return newError("Bro how did you even get here??? Contact language maker asap!") + } } } @@ -443,102 +323,87 @@ func unwrapReturnValue(obj object.Object) object.Object { return obj } -func evalStringInfixExpression(operator string, left, right object.Object) object.Object { - if operator != "+" { - return newError("Operesheni Haielweki: %s %s %s", left.Type(), operator, right.Type()) - } - - leftVal := left.(*object.String).Value - rightVal := right.(*object.String).Value - - return &object.String{Value: leftVal + rightVal} -} - -func evalIndexExpression(left, index object.Object) object.Object { - switch { - case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: - return evalArrayIndexExpression(left, index) - case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: - return newError("Tafadhali tumia number, sio: %s", index.Type()) - case left.Type() == object.DICT_OBJ: - return evalDictIndexExpression(left, index) - default: - return newError("Operesheni hii haiwezekani kwa: %s", left.Type()) - } -} - -func evalArrayIndexExpression(array, index object.Object) object.Object { - arrayObject := array.(*object.Array) - idx := index.(*object.Integer).Value - max := int64(len(arrayObject.Elements) - 1) - - if idx < 0 || idx > max { - return NULL - } - - return arrayObject.Elements[idx] -} - -func evalDictLiteral(node *ast.DictLiteral, env *object.Environment) object.Object { - pairs := make(map[object.HashKey]object.DictPair) - - for keyNode, valueNode := range node.Pairs { - key := Eval(keyNode, env) - if isError(key) { - return key - } - - hashKey, ok := key.(object.Hashable) - if !ok { - return newError("Hashing imeshindikana: %s", key.Type()) - } - - value := Eval(valueNode, env) - if isError(value) { - return value - } - - hashed := hashKey.HashKey() - pairs[hashed] = object.DictPair{Key: key, Value: value} - } - - return &object.Dict{Pairs: pairs} +func evalBreak(node *ast.Break) object.Object { + return BREAK } -func evalDictIndexExpression(dict, index object.Object) object.Object { - dictObject := dict.(*object.Dict) - - key, ok := index.(object.Hashable) - if !ok { - return newError("Samahani, %s haitumiki kama key", index.Type()) - } - - pair, ok := dictObject.Pairs[key.HashKey()] - if !ok { - return NULL - } - - return pair.Value +func evalContinue(node *ast.Continue) object.Object { + return CONTINUE } -func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { - var result object.Object - - for { - condition := Eval(we.Condition, env) - if isError(condition) { - return condition +// func evalForExpression(fe *ast.For, env *object.Environment) object.Object { +// obj, ok := env.Get(fe.Identifier) +// defer func() { // stay safe and not reassign an existing variable +// if ok { +// env.Set(fe.Identifier, obj) +// } +// }() +// val := Eval(fe.StarterValue, env) +// if isError(val) { +// return val +// } + +// env.Set(fe.StarterName.Value, val) + +// // err := Eval(fe.Starter, env) +// // if isError(err) { +// // return err +// // } +// for { +// evaluated := Eval(fe.Condition, env) +// if isError(evaluated) { +// return evaluated +// } +// if !isTruthy(evaluated) { +// break +// } +// res := Eval(fe.Block, env) +// if isError(res) { +// return res +// } +// if res.Type() == object.BREAK_OBJ { +// break +// } +// if res.Type() == object.CONTINUE_OBJ { +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// continue +// } +// if res.Type() == object.RETURN_VALUE_OBJ { +// return res +// } +// err := Eval(fe.Closer, env) +// if isError(err) { +// return err +// } +// } +// return NULL +// } + +func loopIterable(next func() (object.Object, object.Object), env *object.Environment, fi *ast.ForIn) object.Object { + k, v := next() + for k != nil && v != nil { + env.Set(fi.Key, k) + env.Set(fi.Value, v) + res := Eval(fi.Block, env) + if isError(res) { + return res } - - if isTruthy(condition) { - result = Eval(we.Consequence, env) - } else { - break + if res != nil { + if res.Type() == object.BREAK_OBJ { + break + } + if res.Type() == object.CONTINUE_OBJ { + k, v = next() + continue + } + if res.Type() == object.RETURN_VALUE_OBJ { + return res + } } + k, v = next() } - - if result != nil { - return result - } - return nil + return NULL } diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index e2d9771..b6e830d 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -3,10 +3,11 @@ package evaluator import ( "fmt" "testing" + "time" - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/object" - "github.com/AvicennaJr/Nuru/parser" + "github.com/NuruProgramming/Nuru/lexer" + "github.com/NuruProgramming/Nuru/object" + "github.com/NuruProgramming/Nuru/parser" ) func TestEvalIntegerExpression(t *testing.T) { @@ -29,6 +30,20 @@ func TestEvalIntegerExpression(t *testing.T) { } } +func TestEvalFloatExpression(t *testing.T) { + tests := []struct { + input string + expected float64 + }{ + {"2**3", 8.0}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testFloatObject(t, evaluated, tt.expected) + } +} + func TestEvalBooleanExpression(t *testing.T) { tests := []struct { input string @@ -50,6 +65,20 @@ func TestEvalBooleanExpression(t *testing.T) { {"kweli != sikweli", true}, {"sikweli != kweli", true}, {"(1 < 2) == kweli", true}, + {"!kweli", false}, + {"!sikweli", true}, + {"!tupu", true}, + {"!'kitu'", false}, + {"2 > 1 && 1 < 4", true}, + {"2 > 1 && 1 > 4", false}, + {"2 < 1 && 1 < 4", false}, + {"2 < 1 && 1 > 4", false}, + {"5 < 2 || 3 > 2", true}, + {"5 == 5 || 4 == 4", true}, + {"5 > 2 || 3 < 2", true}, + {"5 < 2 || 3 < 2", false}, + {"5 >= 2", true}, + {"5 <= 2", false}, } for _, tt := range tests { @@ -102,6 +131,22 @@ func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { return true } +func testFloatObject(t *testing.T, obj object.Object, expected float64) bool { + result, ok := obj.(*object.Float) + + if !ok { + t.Errorf("Object is not Float, got=%T(%+v)", obj, obj) + return false + } + + if result.Value != expected { + t.Errorf("object has wrong value. got=%f, want=%f", result.Value, expected) + return false + } + + return true +} + func testBooleanObject(t *testing.T, obj object.Object, expected bool) bool { result, ok := obj.(*object.Boolean) if !ok { @@ -174,27 +219,27 @@ func TestErrorHandling(t *testing.T) { }{ { "5 + kweli", - "Aina Hazilingani: NAMBA + BOOLEAN", + "Mstari 1: Aina Hazilingani: NAMBA + BOOLEAN", }, { "5 + kweli; 5;", - "Aina Hazilingani: NAMBA + BOOLEAN", + "Mstari 1: Aina Hazilingani: NAMBA + BOOLEAN", }, { "-kweli", - "Operesheni Haielweki: -BOOLEAN", + "Mstari 1: Operesheni Haieleweki: -BOOLEAN", }, { "kweli + sikweli", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 1: Operesheni Haieleweki: BOOLEAN + BOOLEAN", }, { "5; kweli + sikweli; 5", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 1: Operesheni Haieleweki: BOOLEAN + BOOLEAN", }, { "kama (10 > 1) { kweli + sikweli;}", - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 1: Operesheni Haieleweki: BOOLEAN + BOOLEAN", }, { ` @@ -206,19 +251,19 @@ kama (10 > 1) { rudisha 1; } `, - "Operesheni Haielweki: BOOLEAN + BOOLEAN", + "Mstari 4: Operesheni Haieleweki: BOOLEAN + BOOLEAN", }, { "bangi", - "Neno Halifahamiki: bangi", + "Mstari 1: Neno Halifahamiki: bangi", }, { `"Habari" - "Habari"`, - "Operesheni Haielweki: NENO - NENO", + "Mstari 1: Operesheni Haieleweki: NENO - NENO", }, { - `{"jina": "Avi"}[fn(x) {x}];`, - "Samahani, FUNCTION haitumiki kama key", + `{"jina": "Avi"}[unda(x) {x}];`, + "Mstari 1: Samahani, UNDO (FUNCTION) haitumiki kama ufunguo", }, } @@ -231,8 +276,8 @@ kama (10 > 1) { continue } - if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, tt.expectedMessage) { - t.Errorf("wrong error message, expected=%q, got=%q", tt.expectedMessage, errObj.Message) + if errObj.Message != fmt.Sprintf(tt.expectedMessage) { + t.Errorf("wrong error message, expected=%q, got=%q", fmt.Sprintf(tt.expectedMessage), errObj.Message) } } } @@ -242,10 +287,10 @@ func TestLetStatement(t *testing.T) { input string expected int64 }{ - {"acha a = 5; a;", 5}, - {"acha a = 5 * 5; a;", 25}, - {"acha a = 5; acha b = a; b;", 5}, - {"acha a = 5; acha b = a; acha c = a + b + 5; c;", 15}, + {"fanya a = 5; a;", 5}, + {"fanya a = 5 * 5; a;", 25}, + {"fanya a = 5; fanya b = a; b;", 5}, + {"fanya a = 5; fanya b = a; fanya c = a + b + 5; c;", 15}, } for _, tt := range tests { @@ -254,26 +299,26 @@ func TestLetStatement(t *testing.T) { } func TestFunctionObject(t *testing.T) { - input := "fn(x) { x + 2 ;};" + input := "unda(x) { x + 2 ;};" evaluated := testEval(input) - fn, ok := evaluated.(*object.Function) + unda, ok := evaluated.(*object.Function) if !ok { t.Fatalf("object is not a Function, got=%T(%+v)", evaluated, evaluated) } - if len(fn.Parameters) != 1 { - t.Fatalf("function haas wrong paramters,Parameters=%+v", fn.Parameters) + if len(unda.Parameters) != 1 { + t.Fatalf("function has wrong parameters,Parameters=%+v", unda.Parameters) } - if fn.Parameters[0].String() != "x" { - t.Fatalf("parameter is not x, got=%q", fn.Parameters[0]) + if unda.Parameters[0].String() != "x" { + t.Fatalf("parameter is not x, got=%q", unda.Parameters[0]) } expectedBody := "(x + 2)" - if fn.Body.String() != expectedBody { - t.Fatalf("body is not %q, got=%q", expectedBody, fn.Body.String()) + if unda.Body.String() != expectedBody { + t.Fatalf("body is not %q, got=%q", expectedBody, unda.Body.String()) } } @@ -282,12 +327,12 @@ func TestFunctionApplication(t *testing.T) { input string expected int64 }{ - {"acha mfano = fn(x) {x;}; mfano(5);", 5}, - {"acha mfano = fn(x) {rudisha x;}; mfano(5);", 5}, - {"acha double = fn(x) { x * 2;}; double(5);", 10}, - {"acha add = fn(x, y) {x + y;}; add(5,5);", 10}, - {"acha add = fn(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, - {"fn(x) {x;}(5)", 5}, + {"fanya mfano = unda(x) {x;}; mfano(5);", 5}, + {"fanya mfano = unda(x) {rudisha x;}; mfano(5);", 5}, + {"fanya double = unda(x) { x * 2;}; double(5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5,5);", 10}, + {"fanya add = unda(x, y) {x + y;}; add(5 + 5, add(5, 5));", 20}, + {"unda(x) {x;}(5)", 5}, } for _, tt := range tests { @@ -297,11 +342,11 @@ func TestFunctionApplication(t *testing.T) { func TestClosures(t *testing.T) { input := ` -acha newAdder = fn(x) { - fn(y) { x + y}; +fanya newAdder = unda(x) { + unda(y) { x + y}; }; -acha addTwo = newAdder(2); +fanya addTwo = newAdder(2); addTwo(2); ` testIntegerObject(t, testEval(input), 4) @@ -336,37 +381,57 @@ func TestStringconcatenation(t *testing.T) { } } -func TestBuiltinFunctions(t *testing.T) { - tests := []struct { - input string - expected interface{} - }{ - {`idadi("")`, 0}, - {`idadi("four")`, 4}, - {`idadi("hello world")`, 11}, - {`idadi(1)`, "Samahani, hii function haitumiki na NAMBA"}, - {`idadi("one", "two")`, "Hoja hazilingani, tunahitaji=1, tumepewa=2"}, - } +func TestStringMultiplyInteger(t *testing.T) { + input := `"Mambo" * 4` - for _, tt := range tests { - evaluated := testEval(tt.input) + evaluated := testEval(input) - switch expected := tt.expected.(type) { - case int: - testIntegerObject(t, evaluated, int64(expected)) - case string: - errObj, ok := evaluated.(*object.Error) - if !ok { - t.Errorf("Object is not Error, got=%T(%+v)", evaluated, evaluated) - continue - } - if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, expected) { - t.Errorf("Wrong eror message, expected=%q, got=%q", expected, errObj.Message) - } - } + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not a string, got=%T(%+v)", evaluated, evaluated) + } + + if str.Value != "MamboMamboMamboMambo" { + t.Errorf("String has wrong value, got=%q", str.Value) } } +// func TestBuiltinFunctions(t *testing.T) { +// tests := []struct { +// input string +// expected interface{} +// }{ +// {`jumla()`, "Hoja hazilingani, tunahitaji=1, tumepewa=0"}, +// {`jumla("")`, "Samahani, hii function haitumiki na NENO"}, +// {`jumla(1)`, "Samahani, hii function haitumiki na NAMBA"}, +// {`jumla([1,2,3])`, 6}, +// {`jumla([1,2,3.4])`, 6.4}, +// {`jumla([1.1,2.5,3.4])`, 7}, +// {`jumla([1.1,2.5,"q"])`, "Samahani namba tu zinahitajika"}, +// } + +// for _, tt := range tests { +// evaluated := testEval(tt.input) + +// switch expected := tt.expected.(type) { +// case int: +// testIntegerObject(t, evaluated, int64(expected)) +// case float64: +// testFloatObject(t, evaluated, float64(expected)) + +// case string: +// errObj, ok := evaluated.(*object.Error) +// if !ok { +// t.Errorf("Object is not Error, got=%T(%+v)", evaluated, evaluated) +// continue +// } +// if errObj.Message != fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, expected) { +// t.Errorf("Wrong eror message, expected=%q, got=%q", expected, errObj.Message) +// } +// } +// } +// } + func TestArrayLiterals(t *testing.T) { input := "[1, 2 * 2, 3 + 3]" @@ -403,11 +468,11 @@ func TestArrayIndexExpressions(t *testing.T) { 3, }, { - "acha i = 0; [1][i];", + "fanya i = 0; [1][i];", 1, }, { - "acha myArr = [1, 2, 3]; myArr[2];", + "fanya myArr = [1, 2, 3]; myArr[2];", 3, }, { @@ -432,7 +497,7 @@ func TestArrayIndexExpressions(t *testing.T) { } func TestDictLiterals(t *testing.T) { - input := `acha two = "two"; + input := `fanya two = "two"; { "one": 10 - 9, two: 1 +1, @@ -485,7 +550,7 @@ func TestDictIndexExpression(t *testing.T) { nil, }, { - `acha key = "foo"; {"foo": 5}[key]`, + `fanya key = "foo"; {"foo": 5}[key]`, 5, }, { @@ -516,3 +581,550 @@ func TestDictIndexExpression(t *testing.T) { } } } + +func TestPrefixInteger(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "-4", + -4, + }, + { + "+5", + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + integer, ok := tt.expected.(int) + if !ok { + t.Errorf("Object is not an integer") + } + testIntegerObject(t, evaluated, int64(integer)) + } +} + +func TestPrefixFloat(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "-4.4", + -4.4, + }, + { + "+5.5", + 5.5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + float, ok := tt.expected.(float64) + if !ok { + t.Errorf("Object is not a float") + } + testFloatObject(t, evaluated, float) + } +} + +func TestInExpression(t *testing.T) { + tests := []struct { + input string + expected bool + }{ + { + "'a' ktk 'habari'", + true, + }, + { + "'c' ktk 'habari'", + false, + }, + { + "1 ktk [1, 2, 3]", + true, + }, + { + "4 ktk [1, 2, 3]", + false, + }, + { + "'a' ktk {'a': 'apple', 'b': 'banana'}", + true, + }, + { + "'apple' ktk {'a': 'apple', 'b': 'banana'}", + false, + }, + { + "'c' ktk {'a': 'apple', 'b': 'banana'}", + false, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + testBooleanObject(t, evaluated, tt.expected) + } +} + +func TestArrayConcatenation(t *testing.T) { + tests := []struct { + input string + expected string + }{ + { + "['a', 'b', 'c'] + [1, 2, 3]", + "[a, b, c, 1, 2, 3]", + }, + { + "[1, 2, 3] * 4", + "[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]", + }, + { + "4 * [1, 2, 3]", + "[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + arr, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("Object is not an array, got=%T(%+v)", evaluated, evaluated) + } + + if arr.Inspect() != tt.expected { + t.Errorf("Array has wrong values, got=%s want=%s", arr.Inspect(), tt.expected) + } + } +} + +func TestDictConcatenation(t *testing.T) { + tests := []struct { + input string + expected map[string]string + }{ + { + input: "{'a': 'apple', 'b': 'banana'} + {'c': 'cat'}", + expected: map[string]string{"a": "apple", "b": "banana", "c": "cat"}, + }, + { + input: "{'a':'bbb'} + {'a':'ccc'}", + expected: map[string]string{"a": "ccc"}, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + dict, ok := evaluated.(*object.Dict) + if !ok { + t.Fatalf("Object is not an dict, got=%T(%+v)", evaluated, evaluated) + } + + if len(dict.Pairs) != len(tt.expected) { + t.Errorf("Dictionary has wrong number of pairs, got=%d want=%d", len(dict.Pairs), len(tt.expected)) + } + } +} + +func TestPostfixExpression(t *testing.T) { + inttests := []struct { + input string + expected int64 + }{ + { + "a=5; a++", + 6, + }, + { + "a=5; a--", + 4, + }, + } + + for _, tt := range inttests { + evaluated := testEval(tt.input) + integer, ok := evaluated.(*object.Integer) + if !ok { + t.Fatalf("Object is not an integer, got=%T(%+v)", evaluated, evaluated) + } + testIntegerObject(t, integer, tt.expected) + } + floattests := []struct { + input string + expected float64 + }{ + { + "a=5.5; a++", + 6.5, + }, + { + "a=5.5; a--", + 4.5, + }, + } + + for _, tt := range floattests { + evaluated := testEval(tt.input) + float, ok := evaluated.(*object.Float) + if !ok { + t.Fatalf("Object is not an float, got=%T(%+v)", evaluated, evaluated) + } + testFloatObject(t, float, tt.expected) + } +} + +func TestWhileLoop(t *testing.T) { + input := ` + i = 10 + wakati (i > 0){ + i-- + } + i + ` + + evaluated := testEval(input) + i, ok := evaluated.(*object.Integer) + if !ok { + t.Fatalf("Object is not an integer, got=%T(+%v)", evaluated, evaluated) + } + + if i.Value != 0 { + t.Errorf("Incorrect value, want=0 got=%d", i.Value) + } +} + +func TestForLoop(t *testing.T) { + input := ` + output = "" + kwa i ktk "mojo" { + output += i + } + output + ` + evaluated := testEval(input) + i, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("Object is not a string, got=%T(+%v)", evaluated, evaluated) + } + + if i.Value != "mojo" { + t.Errorf("Wrong value: want=%s got=%s", "mojo", i.Value) + } +} + +func TestBreakLoop(t *testing.T) { + input := ` + i = 0 + wakati (i < 10) { + kama (i == 5) { + vunja + } + i++ + } + i + ` + evaluated := testEval(input) + i, ok := evaluated.(*object.Integer) + if !ok { + t.Fatalf("Object is not an integer, got=%T(+%v)", evaluated, evaluated) + } + + if i.Value != 5 { + t.Errorf("Wrong value: want=5, got=%d", i.Value) + } + + input = ` + output = "" + kwa i ktk "mojo" { + output += i + kama (i == 'o') { + vunja + } + } + output + ` + + evaluatedFor := testEval(input) + j, ok := evaluatedFor.(*object.String) + if !ok { + t.Fatalf("Object is not a string, got=%T", evaluated) + } + + if j.Value != "mo" { + t.Errorf("Wrong value: want=%s, got=%s", "mo", j.Value) + } +} + +func TestContinueLoop(t *testing.T) { + input := ` + i = 0 + wakati (i < 10) { + i++ + kama (i == 5) { + endelea + } + i++ + } + i + ` + evaluated := testEval(input) + i, ok := evaluated.(*object.Integer) + if !ok { + t.Fatalf("Object is not an integer, got=%T(+%v)", evaluated, evaluated) + } + + if i.Value != 11 { + t.Errorf("Wrong value: want=11, got=%d", i.Value) + } + + input = ` + output = "" + kwa i ktk "mojo" { + kama (i == 'o') { + endelea + } + output += i + } + output + ` + + evaluatedFor := testEval(input) + j, ok := evaluatedFor.(*object.String) + if !ok { + t.Fatalf("Object is not a string, got=%T", evaluated) + } + + if j.Value != "mj" { + t.Errorf("Wrong value: want=%s, got=%s", "mj", j.Value) + } +} + +func TestSwitchStatement(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + ` + i = 5 + badili (i) { + ikiwa 2 { + output = 2 + } + ikiwa 5 { + output = 5 + } + kawaida { + output = "haijulikani" + } + } + output + `, + 5, + }, + { + ` + i = 5 + badili (i) { + ikiwa 2 { + output = 2 + } + kawaida { + output = "haijulikani" + } + } + output + `, + "haijulikani", + }, + { + ` + i = 5 + badili (i) { + ikiwa 5 { + output = 5 + } + ikiwa 2 { + output = 2 + } + kawaida { + output = "haijulikani" + } + } + output + `, + 5, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + s, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("Object is not a string, got=%T", evaluated) + } + + if s.Value != tt.expected { + t.Errorf("Wrong Value, want='haijulikani', got=%s", s.Value) + } + + } + } +} + +func TestAssignEqual(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {"a = 5; a += 5", + 10, + }, + { + "a = 5; a -= 5", + 0, + }, + { + "a = 5; a *= 10", + 50, + }, + { + "a = 100; a /= 4", + 25, + }, + { + ` + a = [1, 2, 3] + a[0] += 500 + a[0] + `, + 501, + }, + { + ` + a = "mambo" + a += " vipi" + `, + "mambo vipi", + }, + { + "a = 5.5; a += 4.5", + 10.0, + }, + { + "a = 11.3; a -= 0.8", + 10.5, + }, + { + "a = 0.4; a /= 2", + 0.2, + }, + { + "a = 0.1; a *= 10", + 1.0, + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case float64: + testFloatObject(t, evaluated, float64(expected)) + case string: + s, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("Object not a string, got=%T", evaluated) + } + + if s.Value != tt.expected { + t.Errorf("Wrong value, want=%s, got=%s", tt.expected, s.Value) + } + } + } +} + +func TestStringMethods(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + { + "'mambo'.idadi()", + 5, + }, + { + "'mambo'.herufikubwa()", + "MAMBO", + }, + { + "'MaMbO'.herufindogo()", + "mambo", + }, + { + "'habari'.gawa('a')", + "[h, b, ri]", + }, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + switch eval := evaluated.(type) { + case *object.String: + s, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("Object not of type string, got=%T", eval) + } + if s.Value != tt.expected { + t.Errorf("Wrong value: want=%s, got=%s", tt.expected, s.Value) + } + case *object.Array: + arr, ok := evaluated.(*object.Array) + if !ok { + t.Fatalf("Object not of type array, got=%T", eval) + } + + if arr.Inspect() != tt.expected { + t.Errorf("Wrong value: want=%s, got=%s", tt.expected, arr.Inspect()) + } + } + } + } +} + +func TestTimeModule(t *testing.T) { + input := ` + tumia muda + muda.hasahivi() + ` + + evaluated := testEval(input) + muda, ok := evaluated.(*object.Time) + if !ok { + t.Fatalf("Object is not a time object, got=%T", evaluated) + } + + _, err := time.Parse("15:04:05 02-01-2006", muda.TimeValue) + if err != nil { + t.Errorf("Wrong time value: got=%v", err) + } +} diff --git a/evaluator/forin.go b/evaluator/forin.go new file mode 100644 index 0000000..f5db434 --- /dev/null +++ b/evaluator/forin.go @@ -0,0 +1,29 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalForInExpression(fie *ast.ForIn, env *object.Environment, line int) object.Object { + iterable := Eval(fie.Iterable, env) + existingKeyIdentifier, okk := env.Get(fie.Key) // again, stay safe + existingValueIdentifier, okv := env.Get(fie.Value) + defer func() { // restore them later on + if okk { + env.Set(fie.Key, existingKeyIdentifier) + } + if okv { + env.Set(fie.Value, existingValueIdentifier) + } + }() + switch i := iterable.(type) { + case object.Iterable: + defer func() { + i.Reset() + }() + return loopIterable(i.Next, env, fie) + default: + return newError("Mstari %d: Huwezi kufanya operesheni hii na %s", line, i.Type()) + } +} diff --git a/evaluator/function.go b/evaluator/function.go new file mode 100644 index 0000000..9e9605d --- /dev/null +++ b/evaluator/function.go @@ -0,0 +1,18 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalFunction(node *ast.FunctionLiteral, env *object.Environment) object.Object { + function := &object.Function{ + Name: node.Name, + Parameters: node.Parameters, + Defaults: node.Defaults, + Body: node.Body, + Env: env, + } + + return function +} diff --git a/evaluator/identifier.go b/evaluator/identifier.go new file mode 100644 index 0000000..49f8272 --- /dev/null +++ b/evaluator/identifier.go @@ -0,0 +1,17 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { + if val, ok := env.Get(node.Value); ok { + return val + } + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("Mstari %d: Neno Halifahamiki: %s", node.Token.Line, node.Value) +} diff --git a/evaluator/if.go b/evaluator/if.go new file mode 100644 index 0000000..8a11e81 --- /dev/null +++ b/evaluator/if.go @@ -0,0 +1,22 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { + condition := Eval(ie.Condition, env) + + if isError(condition) { + return condition + } + + if isTruthy(condition) { + return Eval(ie.Consequence, env) + } else if ie.Alternative != nil { + return Eval(ie.Alternative, env) + } else { + return NULL + } +} diff --git a/evaluator/import.go b/evaluator/import.go new file mode 100644 index 0000000..8b5eb23 --- /dev/null +++ b/evaluator/import.go @@ -0,0 +1,90 @@ +package evaluator + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/lexer" + "github.com/NuruProgramming/Nuru/module" + "github.com/NuruProgramming/Nuru/object" + "github.com/NuruProgramming/Nuru/parser" +) + +var searchPaths []string + +func evalImport(node *ast.Import, env *object.Environment) object.Object { + for k, v := range node.Identifiers { + if mod, ok := module.Mapper[v.Value]; ok { + env.Set(k, mod) + } else { + return evalImportFile(k, v, env) + } + } + return NULL +} + +func evalImportFile(name string, ident *ast.Identifier, env *object.Environment) object.Object { + addSearchPath("") + filename := findFile(name) + if filename == "" { + return newError("Moduli %s haipo", name) + } + var scope *object.Environment + scope, err := evaluateFile(filename, env) + if err != nil { + return err + } + return importFile(name, ident, env, scope) +} + +func addSearchPath(path string) { + searchPaths = append(searchPaths, path) +} + +func findFile(name string) string { + basename := fmt.Sprintf("%s.nr", name) + for _, path := range searchPaths { + file := filepath.Join(path, basename) + if fileExists(file) { + return file + } + } + return "" +} + +func fileExists(file string) bool { + _, err := os.Stat(file) + return err == nil +} + +func evaluateFile(file string, env *object.Environment) (*object.Environment, object.Object) { + source, err := os.ReadFile(file) + if err != nil { + return nil, &object.Error{Message: fmt.Sprintf("Tumeshindwa kufungua pakeji: %s", file)} + } + l := lexer.New(string(source)) + p := parser.New(l) + program := p.ParseProgram() + if len(p.Errors()) != 0 { + return nil, &object.Error{Message: fmt.Sprintf("Pakeji %s ina makosa yafuatayo:\n%s", file, strings.Join(p.Errors(), "\n"))} + } + + scope := object.NewEnvironment() + result := Eval(program, scope) + if isError(result) { + return nil, result + } + return scope, nil +} + +func importFile(name string, ident *ast.Identifier, env *object.Environment, scope *object.Environment) object.Object { + value, ok := scope.Get(ident.Value) + if !ok { + return newError("%s sio pakeji", name) + } + env.Set(name, value) + return NULL +} diff --git a/evaluator/in.go b/evaluator/in.go new file mode 100644 index 0000000..22bec49 --- /dev/null +++ b/evaluator/in.go @@ -0,0 +1,81 @@ +package evaluator + +import ( + "strings" + + "github.com/NuruProgramming/Nuru/object" +) + +func evalInExpression(left, right object.Object, line int) object.Object { + switch right.(type) { + case *object.String: + return evalInStringExpression(left, right) + case *object.Array: + return evalInArrayExpression(left, right) + case *object.Dict: + return evalInDictExpression(left, right, line) + default: + return FALSE + } +} + +func evalInStringExpression(left, right object.Object) object.Object { + if left.Type() != object.STRING_OBJ { + return FALSE + } + leftVal := left.(*object.String) + rightVal := right.(*object.String) + found := strings.Contains(rightVal.Value, leftVal.Value) + return nativeBoolToBooleanObject(found) +} + +func evalInDictExpression(left, right object.Object, line int) object.Object { + leftVal, ok := left.(object.Hashable) + if !ok { + return newError("Mstari %d: Huwezi kutumia kama 'key': %s", line, left.Type()) + } + key := leftVal.HashKey() + rightVal := right.(*object.Dict).Pairs + _, ok = rightVal[key] + return nativeBoolToBooleanObject(ok) +} + +func evalInArrayExpression(left, right object.Object) object.Object { + rightVal := right.(*object.Array) + switch leftVal := left.(type) { + case *object.Null: + for _, v := range rightVal.Elements { + if v.Type() == object.NULL_OBJ { + return TRUE + } + } + case *object.String: + for _, v := range rightVal.Elements { + if v.Type() == object.STRING_OBJ { + elem := v.(*object.String) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Integer: + for _, v := range rightVal.Elements { + if v.Type() == object.INTEGER_OBJ { + elem := v.(*object.Integer) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + case *object.Float: + for _, v := range rightVal.Elements { + if v.Type() == object.FLOAT_OBJ { + elem := v.(*object.Float) + if elem.Value == leftVal.Value { + return TRUE + } + } + } + } + return FALSE +} diff --git a/evaluator/index.go b/evaluator/index.go new file mode 100644 index 0000000..ee59c97 --- /dev/null +++ b/evaluator/index.go @@ -0,0 +1,44 @@ +package evaluator + +import "github.com/NuruProgramming/Nuru/object" + +func evalIndexExpression(left, index object.Object, line int) object.Object { + switch { + case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ: + return evalArrayIndexExpression(left, index) + case left.Type() == object.ARRAY_OBJ && index.Type() != object.INTEGER_OBJ: + return newError("Mstari %d: Tafadhali tumia number, sio: %s", line, index.Type()) + case left.Type() == object.DICT_OBJ: + return evalDictIndexExpression(left, index, line) + default: + return newError("Mstari %d: Operesheni hii haiwezekani kwa: %s", line, left.Type()) + } +} + +func evalArrayIndexExpression(array, index object.Object) object.Object { + arrayObject := array.(*object.Array) + idx := index.(*object.Integer).Value + max := int64(len(arrayObject.Elements) - 1) + + if idx < 0 || idx > max { + return NULL + } + + return arrayObject.Elements[idx] +} + +func evalDictIndexExpression(dict, index object.Object, line int) object.Object { + dictObject := dict.(*object.Dict) + + key, ok := index.(object.Hashable) + if !ok { + return newError("Mstari %d: Samahani, %s haitumiki kama ufunguo", line, index.Type()) + } + + pair, ok := dictObject.Pairs[key.HashKey()] + if !ok { + return NULL + } + + return pair.Value +} diff --git a/evaluator/infix.go b/evaluator/infix.go new file mode 100644 index 0000000..2e51052 --- /dev/null +++ b/evaluator/infix.go @@ -0,0 +1,251 @@ +package evaluator + +import ( + "math" + "strings" + + "github.com/NuruProgramming/Nuru/object" +) + +func evalInfixExpression(operator string, left, right object.Object, line int) object.Object { + if right == nil { + return newError("Mstari %d: Umekosea hapa", line) + } + if left == nil { + return newError("Mstari %d: Umekosea hapa", line) + } + switch { + + case operator == "ktk": + return evalInExpression(left, right, line) + + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right, line) + + case operator == "+" && left.Type() == object.DICT_OBJ && right.Type() == object.DICT_OBJ: + leftVal := left.(*object.Dict).Pairs + rightVal := right.(*object.Dict).Pairs + pairs := make(map[object.HashKey]object.DictPair) + for k, v := range leftVal { + pairs[k] = v + } + for k, v := range rightVal { + pairs[k] = v + } + return &object.Dict{Pairs: pairs} + + case operator == "+" && left.Type() == object.ARRAY_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := right.(*object.Array).Elements + elements := append(leftVal, rightVal...) + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.ARRAY_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.Array).Elements + rightVal := int(right.(*object.Integer).Value) + elements := leftVal + for i := rightVal; i > 1; i-- { + elements = append(elements, leftVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.ARRAY_OBJ: + leftVal := int(left.(*object.Integer).Value) + rightVal := right.(*object.Array).Elements + elements := rightVal + for i := leftVal; i > 1; i-- { + elements = append(elements, rightVal...) + } + return &object.Array{Elements: elements} + + case operator == "*" && left.Type() == object.STRING_OBJ && right.Type() == object.INTEGER_OBJ: + leftVal := left.(*object.String).Value + rightVal := right.(*object.Integer).Value + return &object.String{Value: strings.Repeat(leftVal, int(rightVal))} + + case operator == "*" && left.Type() == object.INTEGER_OBJ && right.Type() == object.STRING_OBJ: + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.String).Value + return &object.String{Value: strings.Repeat(rightVal, int(leftVal))} + + case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: + return evalIntegerInfixExpression(operator, left, right, line) + + case left.Type() == object.FLOAT_OBJ && right.Type() == object.FLOAT_OBJ: + return evalFloatInfixExpression(operator, left, right, line) + + case left.Type() == object.INTEGER_OBJ && right.Type() == object.FLOAT_OBJ: + return evalFloatIntegerInfixExpression(operator, left, right, line) + + case left.Type() == object.FLOAT_OBJ && right.Type() == object.INTEGER_OBJ: + return evalFloatIntegerInfixExpression(operator, left, right, line) + + case operator == "==": + return nativeBoolToBooleanObject(left == right) + + case operator == "!=": + return nativeBoolToBooleanObject(left != right) + case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: + return evalBooleanInfixExpression(operator, left, right, line) + + case left.Type() != right.Type(): + return newError("Mstari %d: Aina Hazilingani: %s %s %s", + line, left.Type(), operator, right.Type()) + + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +func evalFloatIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object { + var leftVal, rightVal float64 + if left.Type() == object.FLOAT_OBJ { + leftVal = left.(*object.Float).Value + rightVal = float64(right.(*object.Integer).Value) + } else { + leftVal = float64(left.(*object.Integer).Value) + rightVal = right.(*object.Float).Value + } + + var val float64 + switch operator { + case "+": + val = leftVal + rightVal + case "-": + val = leftVal - rightVal + case "*": + val = leftVal * rightVal + case "**": + val = math.Pow(float64(leftVal), float64(rightVal)) + case "/": + val = leftVal / rightVal + case "%": + val = math.Mod(leftVal, rightVal) + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case "<=": + return nativeBoolToBooleanObject(leftVal <= rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case ">=": + return nativeBoolToBooleanObject(leftVal >= rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } + + if math.Mod(val, 1) == 0 { + return &object.Integer{Value: int64(val)} + } else { + return &object.Float{Value: val} + } +} + +func evalStringInfixExpression(operator string, left, right object.Object, line int) object.Object { + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + + switch operator { + case "+": + return &object.String{Value: leftVal + rightVal} + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalBooleanInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "&&": + return nativeBoolToBooleanObject(leftVal && rightVal) + case "||": + return nativeBoolToBooleanObject(leftVal || rightVal) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", line, left.Type(), operator, right.Type()) + } +} + +func evalFloatInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Float).Value + rightVal := right.(*object.Float).Value + + switch operator { + case "+": + return &object.Float{Value: leftVal + rightVal} + case "-": + return &object.Float{Value: leftVal - rightVal} + case "*": + return &object.Float{Value: leftVal * rightVal} + case "**": + return &object.Float{Value: math.Pow(float64(leftVal), float64(rightVal))} + case "/": + return &object.Float{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case "<=": + return nativeBoolToBooleanObject(leftVal <= rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case ">=": + return nativeBoolToBooleanObject(leftVal >= rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} + +func evalIntegerInfixExpression(operator string, left, right object.Object, line int) object.Object { + leftVal := left.(*object.Integer).Value + rightVal := right.(*object.Integer).Value + + switch operator { + case "+": + return &object.Integer{Value: leftVal + rightVal} + case "-": + return &object.Integer{Value: leftVal - rightVal} + case "*": + return &object.Integer{Value: leftVal * rightVal} + case "**": + return &object.Float{Value: float64(math.Pow(float64(leftVal), float64(rightVal)))} + case "/": + x := float64(leftVal) / float64(rightVal) + if math.Mod(x, 1) == 0 { + return &object.Integer{Value: int64(x)} + } else { + return &object.Float{Value: x} + } + case "%": + return &object.Integer{Value: leftVal % rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case "<=": + return nativeBoolToBooleanObject(leftVal <= rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case ">=": + return nativeBoolToBooleanObject(leftVal >= rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s %s %s", + line, left.Type(), operator, right.Type()) + } +} diff --git a/evaluator/method.go b/evaluator/method.go new file mode 100644 index 0000000..2bf2bd6 --- /dev/null +++ b/evaluator/method.go @@ -0,0 +1,109 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalMethodExpression(node *ast.MethodExpression, env *object.Environment) object.Object { + obj := Eval(node.Object, env) + if isError(obj) { + return obj + } + args := evalExpressions(node.Arguments, env) + if len(args) == 1 && isError(args[0]) { + return args[0] + } + + defs := make(map[string]object.Object) + + for k, v := range node.Defaults { + defs[k] = Eval(v, env) + } + return applyMethod(obj, node.Method, args, defs, node.Token.Line) +} + +func applyMethod(obj object.Object, method ast.Expression, args []object.Object, defs map[string]object.Object, l int) object.Object { + switch obj := obj.(type) { + case *object.String: + return obj.Method(method.(*ast.Identifier).Value, args) + case *object.File: + return obj.Method(method.(*ast.Identifier).Value, args) + case *object.Time: + return obj.Method(method.(*ast.Identifier).Value, args, defs) + case *object.Array: + switch method.(*ast.Identifier).Value { + case "map": + return maap(obj, args) + case "chuja": + return filter(obj, args) + default: + return obj.Method(method.(*ast.Identifier).Value, args) + } + case *object.Module: + if fn, ok := obj.Functions[method.(*ast.Identifier).Value]; ok { + return fn(args, defs) + } + case *object.Instance: + if fn, ok := obj.Package.Scope.Get(method.(*ast.Identifier).Value); ok { + fn.(*object.Function).Env.Set("@", obj) + ret := applyFunction(fn, args, l) + fn.(*object.Function).Env.Del("@") + return ret + } + case *object.Package: + if fn, ok := obj.Scope.Get(method.(*ast.Identifier).Value); ok { + fn.(*object.Function).Env.Set("@", obj) + ret := applyFunction(fn, args, l) + fn.(*object.Function).Env.Del("@") + return ret + } + } + return newError("Samahani, %s haina function '%s()'", obj.Inspect(), method.(*ast.Identifier).Value) +} + +// /////////////////////////////////////////////////////////////// +// //////// Some methods here because of loop dependency //////// +// ///////////////////////////////////////////////////////////// +func maap(a *object.Array, args []object.Object) object.Object { + if len(args) != 1 && args[0].Type() != object.FUNCTION_OBJ { + return newError("Samahani, hoja sii sahihi") + } + + fn, ok := args[0].(*object.Function) + if !ok { + return newError("Samahani, hoja sii sahihi") + } + env := object.NewEnvironment() + newArr := object.Array{Elements: []object.Object{}} + for _, obj := range a.Elements { + env.Set(fn.Parameters[0].Value, obj) + r := Eval(fn.Body, env) + if o, ok := r.(*object.ReturnValue); ok { + r = o.Value + } + newArr.Elements = append(newArr.Elements, r) + } + return &newArr +} + +func filter(a *object.Array, args []object.Object) object.Object { + if len(args) != 1 && args[0].Type() != object.FUNCTION_OBJ { + return newError("Samahani, hoja sii sahihi") + } + + fn, ok := args[0].(*object.Function) + if !ok { + return newError("Samahani, hoja sii sahihi") + } + env := object.NewEnvironment() + newArr := object.Array{Elements: []object.Object{}} + for _, obj := range a.Elements { + env.Set(fn.Parameters[0].Value, obj) + cond := Eval(fn.Body, env) + if cond.Inspect() == "kweli" { + newArr.Elements = append(newArr.Elements, obj) + } + } + return &newArr +} diff --git a/evaluator/package.go b/evaluator/package.go new file mode 100644 index 0000000..07e9bee --- /dev/null +++ b/evaluator/package.go @@ -0,0 +1,18 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalPackage(node *ast.Package, env *object.Environment) object.Object { + pakeji := &object.Package{ + Name: node.Name, + Env: env, + Scope: object.NewEnclosedEnvironment(env), + } + + Eval(node.Block, pakeji.Scope) + env.Set(node.Name.Value, pakeji) + return pakeji +} diff --git a/evaluator/postfix.go b/evaluator/postfix.go new file mode 100644 index 0000000..14763d5 --- /dev/null +++ b/evaluator/postfix.go @@ -0,0 +1,40 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalPostfixExpression(env *object.Environment, operator string, node *ast.PostfixExpression) object.Object { + val, ok := env.Get(node.Token.Literal) + if !ok { + return newError("Tumia KITAMBULISHI CHA NAMBA AU DESIMALI, sio %s", node.Token.Type) + } + switch operator { + case "++": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value + 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '++' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + + } + case "--": + switch arg := val.(type) { + case *object.Integer: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Integer{Value: v}) + case *object.Float: + v := arg.Value - 1 + return env.Set(node.Token.Literal, &object.Float{Value: v}) + default: + return newError("Mstari %d: %s sio kitambulishi cha namba. Tumia '--' na kitambulishi cha namba au desimali.\nMfano:\tfanya i = 2; i++", node.Token.Line, node.Token.Literal) + } + default: + return newError("Haifahamiki: %s", operator) + } +} diff --git a/evaluator/prefix.go b/evaluator/prefix.go new file mode 100644 index 0000000..f46ca91 --- /dev/null +++ b/evaluator/prefix.go @@ -0,0 +1,43 @@ +package evaluator + +import "github.com/NuruProgramming/Nuru/object" + +func evalMinusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: -obj.Value} + + case *object.Float: + return &object.Float{Value: -obj.Value} + + default: + return newError("Mstari %d: Operesheni Haieleweki: -%s", line, right.Type()) + } +} +func evalPlusPrefixOperatorExpression(right object.Object, line int) object.Object { + switch obj := right.(type) { + + case *object.Integer: + return &object.Integer{Value: obj.Value} + + case *object.Float: + return &object.Float{Value: obj.Value} + + default: + return newError("Mstari %d: Operesheni Haieleweki: +%s", line, right.Type()) + } +} + +func evalPrefixExpression(operator string, right object.Object, line int) object.Object { + switch operator { + case "!": + return evalBangOperatorExpression(right) + case "-": + return evalMinusPrefixOperatorExpression(right, line) + case "+": + return evalPlusPrefixOperatorExpression(right, line) + default: + return newError("Mstari %d: Operesheni Haieleweki: %s%s", line, operator, right.Type()) + } +} diff --git a/evaluator/property.go b/evaluator/property.go new file mode 100644 index 0000000..c935c4f --- /dev/null +++ b/evaluator/property.go @@ -0,0 +1,63 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalPropertyExpression(node *ast.PropertyExpression, env *object.Environment) object.Object { + left := Eval(node.Object, env) + if isError(left) { + return left + } + switch left.(type) { + case *object.Instance: + obj := left.(*object.Instance) + prop := node.Property.(*ast.Identifier).Value + if val, ok := obj.Env.Get(prop); ok { + return val + } + case *object.Package: + obj := left.(*object.Package) + prop := node.Property.(*ast.Identifier).Value + if val, ok := obj.Env.Get(prop); ok { + return val + } + // case *object.Module: + // mod := left.(*object.Module) + // prop := node.Property.(*ast.Identifier).Value + // if val, ok := mod.Properties[prop]; ok { + // return val() + // } + } + return newError("Value %s sii sahihi kwenye %s", node.Property.(*ast.Identifier).Value, left.Inspect()) +} + +func evalPropertyAssignment(name *ast.PropertyExpression, val object.Object, env *object.Environment) object.Object { + left := Eval(name.Object, env) + if isError(left) { + return left + } + switch left.(type) { + case *object.Instance: + obj := left.(*object.Instance) + prop := name.Property.(*ast.Identifier).Value + if _, ok := obj.Env.Get(prop); ok { + obj.Env.Set(prop, val) + return NULL + } + obj.Env.Set(prop, val) + return NULL + case *object.Package: + obj := left.(*object.Package) + prop := name.Property.(*ast.Identifier).Value + if _, ok := obj.Env.Get(prop); ok { + obj.Env.Set(prop, val) + return NULL + } + obj.Env.Set(prop, val) + return NULL + default: + return newError("Imeshindikana kuweka kwenye pakiti %s", left.Type()) + } +} diff --git a/evaluator/switch.go b/evaluator/switch.go new file mode 100644 index 0000000..5963d6d --- /dev/null +++ b/evaluator/switch.go @@ -0,0 +1,30 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalSwitchStatement(se *ast.SwitchExpression, env *object.Environment) object.Object { + obj := Eval(se.Value, env) + for _, opt := range se.Choices { + + if opt.Default { + continue + } + for _, val := range opt.Expr { + out := Eval(val, env) + if obj.Type() == out.Type() && obj.Inspect() == out.Inspect() { + blockOut := evalBlockStatement(opt.Block, env) + return blockOut + } + } + } + for _, opt := range se.Choices { + if opt.Default { + out := evalBlockStatement(opt.Block, env) + return out + } + } + return nil +} diff --git a/evaluator/type.go b/evaluator/type.go new file mode 100644 index 0000000..43a3fe0 --- /dev/null +++ b/evaluator/type.go @@ -0,0 +1,72 @@ +package evaluator + +import ( + "strconv" + + "github.com/NuruProgramming/Nuru/object" +) + +func convertToInteger(obj object.Object) object.Object { + switch obj := obj.(type) { + case *object.Integer: + return obj + case *object.Float: + return &object.Integer{Value: int64(obj.Value)} + case *object.String: + i, err := strconv.ParseInt(obj.Value, 10, 64) + if err != nil { + return newError("Haiwezi kubadilisha '%s' kuwa NAMBA", obj.Value) + } + return &object.Integer{Value: i} + case *object.Boolean: + if obj.Value { + return &object.Integer{Value: 1} + } + return &object.Integer{Value: 0} + default: + return newError("Haiwezi kubadilisha %s kuwa NAMBA", obj.Type()) + } +} + +func convertToFloat(obj object.Object) object.Object { + switch obj := obj.(type) { + case *object.Float: + return obj + case *object.Integer: + return &object.Float{Value: float64(obj.Value)} + case *object.String: + f, err := strconv.ParseFloat(obj.Value, 64) + if err != nil { + return newError("Haiwezi kubadilisha '%s' kuwa DESIMALI", obj.Value) + } + return &object.Float{Value: f} + case *object.Boolean: + if obj.Value { + return &object.Float{Value: 1.0} + } + return &object.Float{Value: 0.0} + default: + return newError("Haiwezi kubadilisha %s kuwa DESIMALI", obj.Type()) + } +} + +func convertToString(obj object.Object) object.Object { + return &object.String{Value: obj.Inspect()} +} + +func convertToBoolean(obj object.Object) object.Object { + switch obj := obj.(type) { + case *object.Boolean: + return obj + case *object.Integer: + return &object.Boolean{Value: obj.Value != 0} + case *object.Float: + return &object.Boolean{Value: obj.Value != 0} + case *object.String: + return &object.Boolean{Value: len(obj.Value) > 0} + case *object.Null: + return &object.Boolean{Value: false} + default: + return &object.Boolean{Value: true} + } +} diff --git a/evaluator/while.go b/evaluator/while.go new file mode 100644 index 0000000..89a3a3b --- /dev/null +++ b/evaluator/while.go @@ -0,0 +1,25 @@ +package evaluator + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/object" +) + +func evalWhileExpression(we *ast.WhileExpression, env *object.Environment) object.Object { + condition := Eval(we.Condition, env) + var evaluated object.Object + if isError(condition) { + return condition + } + if isTruthy(condition) { + evaluated = Eval(we.Consequence, env) + if isError(evaluated) { + return evaluated + } + if evaluated != nil && evaluated.Type() == object.BREAK_OBJ { + return evaluated + } + evaluated = evalWhileExpression(we, env) + } + return evaluated +} diff --git a/examples/Astart.nr b/examples/Astart.nr new file mode 100644 index 0000000..2ff03bd --- /dev/null +++ b/examples/Astart.nr @@ -0,0 +1,179 @@ +/*############ A*(A-star) Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +################################################*/ + + +// create a list of numbers +fanya list = unda(first,last,interval){ + fanya list = [first]; + fanya i = first + interval; + wakati(i < last){ + list.sukuma(i) + i+=interval; + } + rudisha list; +} + +// Maths functions +// find the absolute value of a number +fanya abs_namba = unda(namba){ + kama(namba < 0){ + rudisha -1 * namba; + } + + rudisha namba; +} + +// square a number +fanya square = unda(n, i, j){ + fanya kati = (i+j)/2; + fanya mul = kati * kati; + fanya abs_diff = abs_namba(mul-n); + + kama (mul == n || abs_diff < 0.00001){ + rudisha kati; + }au kama(mul < n){ + rudisha square(n,kati,j) + }au{ + rudisha square(n,i,kati) + } +} + +// find the square root of a number +fanya sqrt = unda(namba){ + kwa i ktk list(0,namba,1) { + kama((i*i )== namba){ + rudisha i; + }au kama ((i*i )> namba){ + rudisha square(namba,i-1,i) + } + } +} + +// Main function +fanya aStar = unda(start, goal) { + // Initialize the open and closed lists + fanya openList = [start]; + fanya closedList = []; + + fanya reconstructPath = unda(node) { + fanya path = [node]; + wakati (node["parent"]) { + path = [node["parent"]] + path; + node = node["parent"]; + } + rudisha path; + } + + fanya heuristic = unda(node1, node2) { + // Calculate the Euclidean distance between the nodes' positions + fanya dx = node1["x"] - node2["x"]; + fanya dy = node1["y"] - node2["y"]; + rudisha sqrt(dx * dx + dy * dy); + } + + fanya findMinNode = unda(openList) { + fanya i = 1; + fanya minNode = openList[0]; + + wakati (i < openList.idadi()) { + fanya node = openList[i]; + kama (node["f"] < minNode["f"]) { + minNode = node; + } + i++ + } + + rudisha minNode; + } + + fanya removeNodeFromArray = unda(array, node) { + fanya newArray = []; + fanya i = 1; + wakati (i < array.idadi()) { + kama (array[i] != node) { + newArray.sukuma(array[i]); + } + i++; + } + rudisha newArray; + } + + fanya urefu = unda(node1, node2) { + // Assume all edges have a cost of 1 + rudisha 1; + } + + // Initialize the g and f scores of the starting node + start["g"] = 0; + start["f"] = start["g"] + heuristic(start, goal); + + + + // Start the search loop + wakati (openList.idadi() > 0) { + // Find the node with the lowest f score in the open list + fanya current = findMinNode(openList); + + // Check kama the goal node has been reached + kama (current == goal) { + rudisha reconstructPath(current); + } + + // Move the current node from the open to the closed list + openList = removeNodeFromArray(openList, current); + + closedList.sukuma(current); + + // Explore the neighbors of the current node + kwa neighbor ktk current["neighbors"] { + // Skip neighbors that are in the closed list + kama (neighbor ktk closedList) { + endelea + } + + // Calculate the tentative g score of the neighbor + fanya tentativeG = start["g"] + urefu(current, neighbor); + + // Check kama the neighbor is in the open list + fanya tentativeIsBetter = sikweli; + kama (!(neighbor ktk openList)) { + openList.sukuma(neighbor); + tentativeIsBetter = kweli; + } au kama (tentativeG < neighbor["g"]) { + tentativeIsBetter = kweli; + } + + // Update the neighbor's g score kama the tentative score is better + kama (tentativeIsBetter) { + neighbor["g"] = tentativeG; + neighbor["f"] = neighbor["g"] + heuristic(neighbor, goal); + neighbor["parent"] = current; + } + } + } + + // kama the open list is empty, no path was found + rudisha tupu; +} + +// Define the nodes of the graph +fanya nodeA = { "x": 0, "y": 0, "neighbors": [] }; +fanya nodeB = { "x": 1, "y": 2, "neighbors": [] }; +fanya nodeC = { "x": 3, "y": 1, "neighbors": [] }; +fanya nodeD = { "x": 4, "y": 3, "neighbors": [] }; + +// Define the edges between the nodes +nodeA["neighbors"] = [nodeB]; +nodeB["neighbors"] = [nodeA, nodeC]; +nodeC["neighbors"] = [nodeB, nodeD]; +nodeD["neighbors"] = [nodeC]; + +// Call the A* function with the start and goal nodes and the heuristic and distance functions +//fanya path = aStar(nodeA, nodeC); + +andika(nodeA); \ No newline at end of file diff --git a/examples/example.nr b/examples/example.nr index f8f2c79..5ae7c0b 100644 --- a/examples/example.nr +++ b/examples/example.nr @@ -1,107 +1,439 @@ -andika("Testing basic types..."); -andika(2 + 2); -andika(4 * 4); -acha a = 10; -acha b = 20; +// basics -andika(a + b); +jina = "Nuru" +andika(jina) // Nuru -andika(kweli == sikweli); -andika("Testing empty lines...") -andika(); -andika(); -andika(); -andika("Mambo vipi"); +// lists -andika("Testing Functions... "); +orodha = [1, "pili", kweli] -acha jumlisha = fn(x, y) {x + y}; +namba = [10, 20, 30] +jina = namba[1] // jina is 20 -andika(jumlisha(20,30)); -andika(jumlisha(100,1000)); +namba[1] = 25 -acha zidisha = fn(x, y) {x * y}; +a = [1, 2, 3] +b = [4, 5, 6] +c = a + b // c is now [1, 2, 3, 4, 5, 6] -andika(zidisha(100,1000)); -andika(zidisha(200, 20)); +namba = [10, 20, 30] +andika(20 ktk namba) // will print kweli -// lists can hold any value -andika("Testing lists..."); -acha list = [1, "a", kweli, sikweli]; +namba = [1, 2, 3, 4, 5] -// a few builtins +kwa thamani ktk namba { + andika(thamani) +} + +majina = ["Juma", "Asha", "Haruna"] + +kwa idx, jina ktk majina { + andika(idx, "-", jina) +} + +a = [1, 2, 3] +urefu = a.idadi() +andika(urefu) // will print 3 + +a = [1, 2, 3] +a.sukuma("s", "g") +andika(a) // will print [1, 2, 3, "s", "g"] + +a = [1, 2, 3] +mwisho = a.yamwisho() +andika(mwisho) // will print 3 + +b = [] +mwisho = b.yamwisho() +andika(mwisho) // will print tupu + +andika(1 > 2) // Output: `sikweli` + +andika(1 + 3 < 10) // Output: `kweli` -acha list = sukuma(list, jumlisha(4,5)); +a = 5 +b = 10 +c = 15 -andika(list); -andika(list[2]); -andika(list[-100]); // prints null -andika(idadi(list)); -andika(yamwisho(list)); -andika([1,2,3] + [4,5,6]); -list[0] = 1000 -andika(list[0]) +result = (a < b) && (b < c) -// if statements -andika("Testing if statements..."); -kama (1 > 2) { - andika("Moja ni zaidi ya mbili"); +kama (result) { + andika("Both conditions are true") } sivyo { - andika("Moja si zaidi ya mbili"); + andika("At least one condition is false") +} + +// Output: "Both conditions are true" + +andika(kweli && kweli) // Output: `kweli` + +andika(kweli && sikweli) // Output: `sikweli` + +andika(kweli || sikweli) // Output: `kweli` + +andika(sikweli || sikweli) // Output: `sikweli` + +andika(!kweli) // Output: `sikweli` + +andika(!sikweli) // Output: `kweli` + +namba = [1, 2, 3, 4, 5] + +kwa thamani ktk namba { + kama (thamani % 2 == 0) { + andika(thamani, "is even") + } sivyo { + andika(thamani, "is odd") + } } +// Output: +// 1 is odd +// 2 is even +// 3 is odd +// 4 is even +// 5 is odd -kama (idadi("Habari") == 6) { - andika("Habari yako ndugu"); +salamu = unda() { + jina = jaza("Unaitwa nani? ") + andika("Mambo vipi", jina) } -// fibonacci example -andika("Testing fibonacci..."); +salamu() -acha fibo = fn(x) { - kama (x == 0) { - rudisha 0; - } au kama (x == 1) { - rudisha 1; - } sivyo { - rudisha fibo(x - 1) + fibo(x - 2); - } +aina(2) // Output: "NAMBA" +aina("Nuru") // Output: "NENO" + + +orodha = {"jina": "Juma", "umri": 25} + +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina value": tupu } -andika(fibo(10)); +andika(k[kweli]) // kweli +andika(k["salimu"]("Juma")) // habari Juma + +k['umri'] = 30 +andika(k['umri']) // 30 + +k["lugha"] = "Kiswahili" +andika(k["lugha"]) // Kiswahili + +matunda = {"a": "apple", "b": "banana"} +mboga = {"c": "carrot", "d": "daikon"} +vyakula = matunda + mboga +andika(vyakula) // {"a": "apple", "b": "banana", "c": "carrot", "d": "daikon"} -// testing input -andika("Testing input from user..."); -acha salamu = fn() { - acha jina = jaza("Unaitwa nani rafiki? "); - rudisha "Mambo vipi " + jina; - } -andika(salamu()); +"umri" ktk k // kweli +"urefu" ktk k // sikweli + + +hobby = {"a": "asili", "b": "baiskeli", "c": "chakula"} +kwa i, v ktk hobby { + andika(i, "=>", v) +} +/* a => asili + b => baiskeli + c => chakula */ + + +kwa v ktk hobby { + andika(v) +} /* -Multiline comment +asili +baiskeli +chakula */ -// test dictionaries +jina = "lugano" + +kwa i ktk jina { + andika(i) +} + +kamusi = {"a": "andaa", "b": "baba"} -andika("Testing dictionaries...") +kwa v ktk kamusi { + andika(v) +} + + +kwa k, v ktk kamusi { + andika(k + " ni " + v) +} + +kwa v ktk "mojo" { + andika(v) +} + +kwa i, v ktk "mojo" { + andika(i, "->", v) +} + +majina = ["juma", "asha", "haruna"] + +kwa v ktk majina { + andika(v) +} + +kwa i, v ktk majina { + andika(i, "-", v) +} + + +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimevunja") + vunja + } + andika(v) +} + +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimeruka") + endelea + } + andika(v) +} + +jum = unda(x, y) { + rudisha x + y +} -acha watu = [{"jina": "Mojo", "kabila": "Mnyakusa"}, {"jina": "Avi", "kabila": "Mwarabu wa dubai"}] +jum(2, 3) // 5 + +salamu = unda() { + andika("Habari yako") +} -andika(watu, watu[0], watu[0]["jina"], watu[0]["kabila"]) +salamu() -watu[0]["jina"] = "MJ"; -andika(watu[0]["jina"]); +salamu = unda(jina) { + andika("Habari yako", jina) +} -andika({"a":1} + {"b": 2}) -// testing while loop +salamu("asha") // Habari yako asha -andika("Testing while loop..."); +salimu = unda(salamu="Habari") { + andika(salamu) +} -acha i = 10; +salimu() // Habari +salimu("Mambo") // Mambo -wakati (i > 0) { - andika(i); - i = i - 1; +mfano = unda(x) { + rudisha "nimerudi" + andika(x) } + +mfano("x") // nimerudi + + +fib = unda(n) { + kama (n <= 1) { + rudisha n + } sivyo { + rudisha fib(n-1) + fib(n-2) + } +} + +andika(fib(10)) // 55 + +jum = unda(x) { + rudisha unda(y) { + rudisha x + y + } +} + +jum_x = jum(5) +andika(jum_x(3)) // 8 + +2 + 3 * 5 // 17 + +a = 2.5 +b = 3/5 + +a + b // 2.8 + +i = 2.4 + +i++ // 3.4 + +i = 2 + +i *= 3 // 6 +i /= 2 // 3 +i += 100 // 103 +i -= 10 // 93 +i %= 90 // 3 + +i = -10 + +wakati (i < 0) { + andika(i) + i++ +} + + +andika("mambo") // mambo + +a = 'niaje' + +andika("mambo", a) // mambo niaje + +a = "habari" + " " + "yako" + +andika(a) // habari yako + +b = "habari" + +b += " yako" + +// habari yako + +andika("mambo " * 4) + +// mambo mambo mambo mambo + +a = "habari" + +a *= 4 + +// habarihabarihabarihabari + +jina = "avicenna" + +kwa i ktk jina {andika(i)} + +a = "nuru" + +andika(a == "nuru") // kweli + +andika(a == "mambo") // sikweli + +a = "mambo" +a.idadi() // 5 + +a = "NURU" +a.herufindogo() // nuru + +a = "nuru mambo habari" +b = a.gawa() +andika(b) // ["nuru", "mambo", "habari"] + +a = "nuru,mambo,habari" +b = a.gawa(",") +andika(b) // ["nuru", "mambo", "habari"] + +a = 2 + +badili (a){ + ikiwa 3 { + andika("a ni tatu") + } + ikiwa 2 { + andika ("a ni mbili") + } +} + +badili (a) { + ikiwa 1,2,3 { + andika("a ni kati ya 1, 2 au 3") + } + ikiwa 4 { + andika("a ni 4") + } +} + +z = 20 + +badili(z) { + ikiwa 10 { + andika("kumi") + } + ikiwa 30 { + andika("thelathini") + } + kawaida { + andika("ishirini") + } +} + +i = 1 + +wakati (i <= 5) { + andika(i) + i++ +} + +i = 1 + +wakati (i < 5) { + kama (i == 3) { + andika("nimevunja") + vunja + } + andika(i) + i++ +} + +i = 0 + +wakati (i < 5) { + i++ + kama (i == 3) { + andika("nimeruka") + endelea + } + andika(i) +} + +// using time: + +tumia muda + +andika(muda.hasahivi()) +andika("Tunalala") +muda.lala(2) +andika("Tumeamka") + +s = muda.hasahivi() +muda.lala(2) +andika(muda.tangu(s)) + +andika(s.ongeza(sekunde=3, miaka=10, dakika=4)) + +// using file: + +fail = fungua("sarufi.nr") + +andika(fail.soma()) + +// using net and json: + +tumia mtandao, jsoni +url = "https://v2.jokeapi.dev/joke/Any?type=single" + +resp = mtandao.peruzi(url) + +resp = jsoni.dikodi(resp) + +andika(resp["joke"]) + + +// os module + +tumia os + +andika(os.kimbiza("pwd")) + +os.toka(0) diff --git a/examples/perceptron.nr b/examples/perceptron.nr new file mode 100644 index 0000000..bc01b54 --- /dev/null +++ b/examples/perceptron.nr @@ -0,0 +1,188 @@ +tumia hisabati + +// Mbinu za Kupanga +//orodhesha(kwanza, mwisho, umbali), huunda orodha ya nambari na umbali uliowekwa kati yao. +fanya orodhesha = unda(kwanza, mwisho, umbali){ + fanya orodha = [kwanza]; + fanya i = kwanza + umbali; + wakati(i < mwisho){ + orodha.sukuma(i); + i += umbali; + } + rudisha orodha; +} + +// Kuanzisha uzani bila mpangilio +fanya mizani = [ + hisabati.random() * 2 - 1, + hisabati.random() * 2 - 1, + hisabati.random() * 2 - 1 +]; + +// Undo la uanzishaji wa Sigmoid +fanya sigmoid = unda(vekta) { + fanya tokeo = []; + kwa v ktk vekta { + tokeo.sukuma(1 / (1 + hisabati.exp(-1 * v))); + } + rudisha tokeo; +} + +// Derivative ya undo la sigmoid +fanya sigmoidDerivative = unda(vekta) { + andika("vekta: ",vekta) + fanya tokeo = []; + kwa v ktk vekta { + tokeo.sukuma(v * (1 - v)); + } + + rudisha tokeo; +} + +fanya kuzidishaMatrikiVekta = unda(matriki, vekta) { + fanya tokeo = []; + kwa row ktk matriki { + fanya jamii = 0; + kwa j, kipengee ktk row { + jamii += kipengee * vekta[j]; + } + tokeo.sukuma(jamii); + } + rudisha tokeo; +} + +fanya zidishaKwaNukta = unda(safu1, safu2) { + // Angalia ikiwa safu zina urefu sawa + kama (safu1.idadi() != safu2.idadi()) { + andika("Safu lazima ziwe na urefu sawa kwa kuzidisha kwa busara ya kipengele."); + } + + // Perform element-wise multiplication + fanya tokeo = []; + kwa i, kipengee ktk safu1 { + tokeo.sukuma(kipengee * safu2[i]); + } + rudisha tokeo; +} + +// Songa mbele kupitia mtandao wa neva +fanya waza = unda(pembejeo, mizani) { + fanya jumlaYaUzani = sigmoid(kuzidishaMatrikiVekta(pembejeo, mizani)); + rudisha jumlaYaUzani; +} + +fanya badiliMatriki = unda(matrix) { + // Pata idadi ya safu mlalo na safu wima katika matrix asili + fanya nambari_ya_safu_mlalo = matrix.idadi(); + fanya nambari_ya_safu_wima = matrix[0].idadi(); + + // Unda matrix mpya na safu mlalo na safu wima zilizobadilishwa + fanya matrikiIliyobadili = []; + + // Pita ndani ya safu wima + kwa safu_wima_ya, safuW ktk orodhesha(0, nambari_ya_safu_wima, 1){ + // Anzisha safu mlalo mpya kwa matriki iliyopitishwa + fanya transposed_safu_mlalo = []; + + // Pita ndani ya safu mlalo + kwa safu_mlalo_ya, safu ktk orodhesha(0, nambari_ya_safu_mlalo, 1){ + // Sukuma kipengele kwenye safu wima ya sasa na safu mlalo hadi safu iliyopitishwa + transposed_safu_mlalo.sukuma(matrix[safu_mlalo_ya][safu_wima_ya]); + } + + // Sukuma safu mlalo iliyopitishwa kwenye matriki lililopitishwa + matrikiIliyobadili.sukuma(transposed_safu_mlalo); + } + + rudisha matrikiIliyobadili; +} + +// Funza mtandao wa neva +fanya funza = unda(mizani, mafunzoPembejeo, matokeoYaMafunzo, marudioYaMafunzo) { + fanya kurudia = 0 + + andika('\nmafunzoPembejeo: '); + andika(mafunzoPembejeo); + + andika('\nmatokeoYaMafunzo: '); + andika(matokeoYaMafunzo); + + + fanya orodha = orodhesha(0, marudioYaMafunzo, 1) + // andika("orodha: ",orodha) + + kwa i ktk orodha{ + andika('\n\nkurudia: '); + andika(i); + // Pitisha mafunzo yaliyowekwa kupitia mtandao wa neva + fanya pato = waza(mafunzoPembejeo, mizani); + + andika('\npato: '); + andika(pato); + + // Kuhesabu kiwango cha upungufu + fanya upungufu = []; + kwa i, kipengee ktk matokeoYaMafunzo { + upungufu.sukuma(kipengee - pato[i]); + } + + andika('\nupungufu: '); + andika(upungufu); + + fanya sigmoidDerivative_ya_pato = sigmoidDerivative(pato) + + andika('\nsigmoidDerivative tokeo: '); + andika(sigmoidDerivative_ya_pato); + + fanya zidishaKwaNukta_tokeo = zidishaKwaNukta(upungufu, sigmoidDerivative_ya_pato); + + andika('\nzidishaKwaNukta tokeo: '); + andika(zidishaKwaNukta_tokeo); + + fanya mafunzoPembejeoYaliyobadili = badiliMatriki(mafunzoPembejeo) + + andika('\nmafunzo pembejeo yaliyobadili: '); + andika(mafunzoPembejeoYaliyobadili); + + // Kuzidisha upungufu kwa pembejeo na upinde rangi ya kitendakazi cha sigmoid + // Uzito mdogo wa ujasiri hurekebishwa zaidi kupitia asili ya kazi + fanya marekebisho = kuzidishaMatrikiVekta(mafunzoPembejeo, zidishaKwaNukta_tokeo); + andika('\nmarekebisho tokeo: '); + andika(marekebisho); + + + // Rekebisha uzani + kwa i, j ktk mizani { + mizani[i] = mizani[i] + marekebisho[i]; + } + + andika('\nmizani mpya: '); + andika(mizani); + kurudia++ + } + + rudisha mizani; +} + + +andika('\nMizani ya Kuanzisha isiyo na mpangilio: '); +andika(mizani); + +// Seti ya mafunzo +fanya mafunzoPembejeo = [[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]]; +fanya matokeoYaMafunzo = [0, 1, 1, 0]; + +// Funza mtandao wa neva +fanya mafunzoMizani = funza(mizani, mafunzoPembejeo, matokeoYaMafunzo, 10000); + +andika('\nMizani baada ya mafunzo:'); +andika(mafunzoMizani); + +// Ingizo la mtumiaji kwa hali mpya +fanya A = 0; +fanya B = 0; +fanya C = 1; + +andika('\nHali mpya: data ya pembejeo = ', A, B, C); +andika('\nData ya pato:'); +andika(waza([[A, B, C]], mafunzoMizani)); diff --git a/examples/reduce.nr b/examples/reduce.nr new file mode 100644 index 0000000..2f09d00 --- /dev/null +++ b/examples/reduce.nr @@ -0,0 +1,34 @@ +fanya reduce = unda(iterator, callback, initialValue) { + fanya accumulator = initialValue; + + kwa thamani ktk iterator { + accumulator = callback(accumulator, thamani); + } + + rudisha accumulator; +} + +fanya list = [1,2,3,4,5]; +fanya employees = [{"salary":120},{"salary":135},{"salary":140}] + +fanya sum = unda(acc,value){ + rudisha acc + value; +} + +fanya mul = unda(acc,value){ + rudisha acc * value; +} + +fanya sumSalo = unda(acc,value){ + rudisha acc + value["salary"]; +} + +fanya sumSaloWithTax = unda(acc,value){ + rudisha acc + (value["salary"] * (1-0.34)); +} + +andika(reduce(list,sum,0)) +andika(reduce(list,mul,1)) + +andika(reduce(employees,sumSalo,0)) +andika(reduce(employees,sumSaloWithTax,0)) \ No newline at end of file diff --git a/examples/sarufi.nr b/examples/sarufi.nr new file mode 100644 index 0000000..39c7419 --- /dev/null +++ b/examples/sarufi.nr @@ -0,0 +1,32 @@ +tumia mtandao +tumia jsoni +pakeji sarufi { + andaa = unda(file) { + config = fungua(file) + configString = config.soma() + configDict = jsoni.dikodi(configString) + clientID = configDict["client_id"] + clientSecret = configDict["client_secret"] + params = {"client_id": clientID, "client_secret": clientSecret} + tokenString = mtandao.tuma(yuareli="https://api.sarufi.io/api/access_token", mwili=params) + tokenDict = jsoni.dikodi(tokenString) + @.token = tokenDict["access_token"] + @.Auth = "Bearer " + @.token + } + + tokenYangu = unda() { + rudisha @.token + } + + tengenezaChatbot = unda(data) { + majibu = mtandao.tuma(yuareli="https://api.sarufi.io/chatbot", vichwa={"Authorization": @.Auth}, mwili = data) + rudisha majibu + } + + pataChatbotZote = unda() { + majibu = mtandao.peruzi(yuareli="https://api.sarufi.io/chatbots", vichwa={"Authorization": @.Auth}) + rudisha majibu + } +} + + diff --git a/examples/sorting_algorithm.nr b/examples/sorting_algorithm.nr new file mode 100644 index 0000000..b86f10c --- /dev/null +++ b/examples/sorting_algorithm.nr @@ -0,0 +1,63 @@ +/* +############ Sorting Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +############################################# +*/ + +slice = unda(arr,start, end) { + result = [] + wakati (start < end) { + result = result + [arr[start]] + start = start + 1 + } + rudisha result +} + +merge = unda(left, right) { + result = [] + lLen = left.idadi() + rLen = right.idadi() + l = 0 + r = 0 + wakati (l < lLen && r < rLen) { + kama (left[l] < right[r]) { + result = result + [left[l]] + l = l + 1 + } sivyo { + result = result + [right[r]] + r = r + 1 + } + } + andika(result) +} + + +mergeSort = unda(arr){ + len = arr.idadi() + andika("arr is ", arr," of length ", len) + kama (len < 2) { + rudisha arr + } + andika("len is greater than or == to 2", len > 1) + + mid = (len / 2) + andika("arr has a mid point of ", mid) + + left = slice(arr, 0, mid) + right = slice(arr, mid, len) + andika("left slice is ", left) + andika("right slice is ", right) + sortedLeft = mergeSort(left) + sortedRight = mergeSort(right) + andika("sortedLeft is ", sortedLeft) + andika("sortedRight is ", sortedRight) + rudisha merge(sortedLeft, sortedRight) +} + +arr = [6, 5, 3, 1, 8, 7, 2, 4] +sortedArray = mergeSort(arr) +andika(sortedArray) diff --git a/examples/sudoku_solver.nr b/examples/sudoku_solver.nr new file mode 100644 index 0000000..de5d3a6 --- /dev/null +++ b/examples/sudoku_solver.nr @@ -0,0 +1,101 @@ +/*########### Backtracking Algorithm ############## + + By @VictorKariuki + + https://github.com/VictorKariuki + +NURU program to solve Sudoku using Backtracking Algorithm + +The sudoku puzzle is represented as a 2D array. The empty +cells are represented by 0. The algorithm works by trying +out all possible numbers for an empty cell. If the number +is valid, it is placed in the cell. If the number is invalid, +the algorithm backtracks to the previous cell and tries +another number. The algorithm terminates when all cells +are filled. The algorithm is implemented in the solveSudoku +function. The isValid function checks kama a number is +valid in a given cell. The printSudoku function prints +the sudoku puzzle. The solveSudoku function solves the +sudoku puzzle. The main function initializes the sudoku +puzzle and calls the solveSudoku function. + +#################################################*/ + + +fanya printing = unda(sudoku) { + fanya row = 0 + wakati (row < 9){ + andika(sudoku[row]) + row++ + } +} + +fanya sudoku = [[3, 0, 6, 5, 0, 8, 4, 0, 0],[5, 2, 0, 0, 0, 0, 0, 0, 0],[0, 8, 7, 0, 0, 0, 0, 3, 1],[0, 0, 3, 0, 1, 0, 0, 8, 0],[9, 0, 0, 8, 6, 3, 0, 0, 5],[0, 5, 0, 0, 9, 0, 6, 0, 0],[1, 3, 0, 0, 0, 0, 2, 5, 0],[0, 0, 0, 0, 0, 0, 0, 7, 4],[0, 0, 5, 2, 0, 6, 3, 0, 0]] + + + +fanya isSafe = unda(grid, row, col, num) { + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[row][x] == num) { + rudisha sikweli + } + } + + kwa x ktk [0,1,2,3,4,5,6,7,8] { + kama (grid[x][col] == num) { + rudisha sikweli + } + } + + fanya startRow = row - row % 3 + fanya startCol = col - col % 3 + + kwa i ktk [0, 1, 2] { + kwa j ktk [0, 1, 2] { + kama (grid[i + startRow][j + startCol] == num) { + rudisha sikweli + } + } + } + + rudisha kweli +} + +fanya solveSudoku = unda(grid, row, col) { + kama (row == 8 && col == 9) { + rudisha kweli + } + + kama (col == 9) { + row += 1 + col = 0 + } + + kama (grid[row][col] > 0) { + rudisha solveSudoku(grid, row, col + 1) + } + + kwa num ktk [1,2,3,4,5,6,7,8,9] { + kama (isSafe(grid, row, col, num)) { + grid[row][col] = num + kama (solveSudoku(grid, row, col + 1)) { + rudisha kweli + } + } + + grid[row][col] = 0 + } + + rudisha sikweli +} +andika() +andika("----- PUZZLE TO SOLVE -----") +printing(sudoku) +kama (solveSudoku(sudoku, 0, 0)){ + andika() + andika("--------- SOLUTION --------") + printing(sudoku) + andika() +} sivyo { + andika("imeshindikana") +} \ No newline at end of file diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 0000000..6b8ccb2 --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,17 @@ +# Nuru Extensions For Various Editors + +## [VSCODE](./vscode/) + +Nuru syntax highlighting on VSCode + +## [VIM](./vim) + +The file contained herein has a basic syntax highlight for vim. +The file should be saved in `$HOME/.vim/syntax/nuru.vim`. +You should add the following line to your `.vimrc` or the appropriate location: + +```vim +au BufRead,BufNewFile *.nr set filetype=nuru +``` + +Only basic syntax highlighting is provided by the script. diff --git a/extensions/vim/syntax/nuru.vim b/extensions/vim/syntax/nuru.vim new file mode 100644 index 0000000..a3d60f0 --- /dev/null +++ b/extensions/vim/syntax/nuru.vim @@ -0,0 +1,51 @@ +" Sintaksia ya nuru kwenye programu ya "vim" +" Lugha: Nuru + +" Maneno tengwa +syntax keyword nuruKeyword unda pakeji rudisha vunja endelea tupu +syntax keyword nuruType fanya +syntax keyword nuruBool kweli sikweli +syntax keyword nuruConditional kama sivyo au +syntax match nuruComparision /[!\|<>]/ +syntax keyword nuruLoop ktk while badili +syntax keyword nuruLabel ikiwa kawaida + +" Nambari +syntax match nuruInt '[+-]\d\+' contained display +syntax match nuruFloat '[+-]\d+\.\d*' contained display + +" Viendeshaji +syntax match nuruAssignment '=' +syntax match nuruLogicalOP /[\&!|]/ + +" Vitendakazi +syntax keyword nuruFunction andika aina jaza fungua + +" Tungo +syntax region nuruString start=/"/ skip=/\\"/ end=/"/ +syntax region nuruString start=/'/ skip=/\\'/ end=/'/ + +" Maoni +syntax match nuruComment "//.*" +syntax region nuruComment start="/\*" end="\*/" + +" Fafanua sintaksia +let b:current_syntax = "nuru" + +highlight def link nuruComment Comment +highlight def link nuruBool Boolean +highlight def link nuruFunction Function +highlight def link nuruComparision Conditional +highlight def link nuruConditional Conditional +highlight def link nuruKeyword Keyword +highlight def link nuruString String +highlight def link nuruVariable Identifier +highlight def link nuruLoop Repeat +highlight def link nuruInt Number +highlight def link nuruFloat Float +highlight def link nuruAssignment Operator +highlight def link nuruLogicalOP Operator +highlight def link nuruAriOP Operator +highlight def link nuruType Type +highlight def link nuruLabel Label + diff --git a/extensions/vscode/CHANGELOG.md b/extensions/vscode/CHANGELOG.md new file mode 100644 index 0000000..f0f7836 --- /dev/null +++ b/extensions/vscode/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "nuru" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/extensions/vscode/README.md b/extensions/vscode/README.md new file mode 100644 index 0000000..c0c1c79 --- /dev/null +++ b/extensions/vscode/README.md @@ -0,0 +1,24 @@ +# Nuru VSCode Extension + +This is a syntax highliting extension for Nuru on vscode. It detects `.nr` and `.sw` files. + +## Screenshots +

+Nuru Programming Language +

+ +## How To Install + +### Download From Market Place + +- Simply download the Nuru Extension from VSCode Market Place + +### Windows + +- Copy the whole [nuru folder](https://github.com/NuruProgramming/Nuru/tree/main/extensions/vscode/nuru) and paste it in the VSCode extensions directory found in `%USERPROFILE%\.vscode\extensions` +- Restart VSCode + +### Linux and MacOS + +- Copy the whole [nuru folder](https://github.com/NuruProgramming/Nuru/tree/main/extensions/vscode/nuru) and paste it in the VSCode extensions directory found in `~/.vscode/extensions` +- Restart VSCode diff --git a/extensions/vscode/assets/screenshot.png b/extensions/vscode/assets/screenshot.png new file mode 100644 index 0000000..c115919 Binary files /dev/null and b/extensions/vscode/assets/screenshot.png differ diff --git a/extensions/vscode/nuru/language-configuration.json b/extensions/vscode/nuru/language-configuration.json new file mode 100644 index 0000000..07cc94f --- /dev/null +++ b/extensions/vscode/nuru/language-configuration.json @@ -0,0 +1,72 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//", + // symbols used for start and end a block comment. Remove this entry if your language does not support block comments + "blockComment": [ + "/*", + "*/" + ] + }, + // symbols used as brackets + "brackets": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + [ + "{", + "}" + ], + [ + "[", + "]" + ], + [ + "(", + ")" + ], + [ + "\"", + "\"" + ], + [ + "'", + "'" + ] + ] +} \ No newline at end of file diff --git a/extensions/vscode/nuru/package.json b/extensions/vscode/nuru/package.json new file mode 100644 index 0000000..736cee1 --- /dev/null +++ b/extensions/vscode/nuru/package.json @@ -0,0 +1,35 @@ +{ + "name": "nuru", + "displayName": "Nuru", + "description": "Nuru Programming Language", + "version": "0.0.1", + "engines": { + "vscode": "^1.74.0" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "languages": [ + { + "id": "nuru", + "aliases": [ + "Nuru Programming Language", + "nuru" + ], + "extensions": [ + ".nr", + ".sw" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "nuru", + "scopeName": "source.nr", + "path": "./syntaxes/nuru.tmLanguage.json" + } + ] + } +} \ No newline at end of file diff --git a/extensions/vscode/nuru/syntaxes/nuru.tmLanguage.json b/extensions/vscode/nuru/syntaxes/nuru.tmLanguage.json new file mode 100644 index 0000000..17ef83c --- /dev/null +++ b/extensions/vscode/nuru/syntaxes/nuru.tmLanguage.json @@ -0,0 +1,156 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Nuru Programming Language", + "scopeName": "source.nr", + "fileTypes": [ + "nr" + ], + "patterns": [ + { + "include": "#linecomments" + }, + { + "include": "#blockcomments" + }, + { + "include": "#operators" + }, + { + "include": "#punctuation" + }, + { + "include": "#numbers" + }, + { + "include": "#constant" + }, + { + "include": "#keywords" + }, + { + "include": "#strings" + }, + { + "include": "#identifiers" + } + ], + "repository": { + "linecomments": { + "name": "comment.line.double-slash.nuru", + "match": "(//).*$\n?", + "captures": { + "1": { + "name": "punctuation.definition.comment.nuru" + } + } + }, + "blockcomments": { + "name": "comment.block.nuru", + "begin": "/\\*", + "end": "\\*/", + "captures": { + "0": { + "name": "punctuation.definition.comment.nuru" + } + } + }, + "numbers": { + "name": "constant.numeric.nuru", + "match": "\\b[0-9]+(\\.[0-9]+)?\\b" + }, + "constant": { + "name": "constant.language.nuru", + "match": "\\b(kweli|sikweli)\\b" + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.arithmetic.nuru", + "match": "\\b(\\+|\\-|%|\\*|\\/|\\^)\\b" + }, + { + "name": "keyword.operator.logical.nuru", + "match": "\\b(==|<=|>=|<|>|&&|\\|\\|)\\b" + }, + { + "name": "keyword.operator.assignment.nuru", + "match": "\\b(=|:)\\b" + }, + { + "name": "punctuation.accessor.nuru", + "match": "\\." + } + ] + }, + "punctuation": { + "patterns": [ + { + "name": "punctuation.separator", + "match": "," + }, + { + "include": "#groups" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "storage.type.function.nuru", + "match": "\\b(unda|andika|idadi|jumla|yamwisho|sukuma|jaza|aina|fungua)\\b" + }, + { + "name": "storage.type.nuru", + "match": "\\bfanya\\b" + }, + { + "name": "keyword.control.nuru", + "match": "\\b(kama|au|sivyo|wakati|rudisha|vunja|endelea|tupu|ktk|kwa|badili|ikiwa|kawaida|tumia)\\b" + }, + { + "name": "support.function.nuru", + "match": "\\b(os|muda)\\b" + } + ] + }, + "identifiers": { + "patterns": [ + { + "name": "meta.functioncall.nuru", + "match": "\\b([_A-Za-z][_A-Za-z0-9]*)\\b(?=\\()", + "captures": { + "1": { + "name": "entity.name.function.nuru" + } + } + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.nuru", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.nuru", + "match": "\\\\." + } + ] + }, + { + "name": "string.quoted.single.nuru", + "begin": "\\'", + "end": "\\'", + "patterns": [ + { + "name": "constant.character.escape.nuru", + "match": "\\\\." + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/go.mod b/go.mod index 352462e..1ea346a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,44 @@ -module github.com/AvicennaJr/Nuru +module github.com/NuruProgramming/Nuru go 1.18 + +require ( + github.com/AvicennaJr/GoPrompt v0.0.0-20230411215003-be2316d88e2d + github.com/charmbracelet/bubbles v0.15.0 + github.com/charmbracelet/bubbletea v0.23.2 + github.com/charmbracelet/glamour v0.6.0 + github.com/charmbracelet/lipgloss v0.7.1 + github.com/lrstanley/bubblezone v0.0.0-20230303230241-08f906ff62a9 +) + +require ( + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/containerd/console v1.0.3 // indirect + github.com/dlclark/regexp2 v1.4.0 // indirect + github.com/gorilla/css v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.7 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-tty v0.0.3 // indirect + github.com/microcosm-cc/bluemonday v1.0.21 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/term v1.2.0-beta.2 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/sahilm/fuzzy v0.1.0 // indirect + github.com/yuin/goldmark v1.5.2 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect + golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/sys v0.7.0 // indirect + golang.org/x/term v0.7.0 // indirect + golang.org/x/text v0.9.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3125924 --- /dev/null +++ b/go.sum @@ -0,0 +1,125 @@ +github.com/AvicennaJr/GoPrompt v0.0.0-20230411215003-be2316d88e2d h1:H+Y1MQQXd83x0xC2MPOw+gFFozKTXgcW69bV80+/wpY= +github.com/AvicennaJr/GoPrompt v0.0.0-20230411215003-be2316d88e2d/go.mod h1:oo6I+tik505nlWIPCU2ogWMkOxoTt7A1YwQYlmlHqE8= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= +github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= +github.com/charmbracelet/bubbletea v0.23.2 h1:vuUJ9HJ7b/COy4I30e8xDVQ+VRDUEFykIjryPfgsdps= +github.com/charmbracelet/bubbletea v0.23.2/go.mod h1:FaP3WUivcTM0xOKNmhciz60M6I+weYLF76mr1JyI7sM= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lrstanley/bubblezone v0.0.0-20230303230241-08f906ff62a9 h1:w7W7F0EBRNTRRy+MFNLGrhJLn2Ldzl8Ms2AtHtgFtuw= +github.com/lrstanley/bubblezone v0.0.0-20230303230241-08f906ff62a9/go.mod h1:v5lEwWaguF1o2MW/ucO0ZIA/IZymdBYJJ+2cMRLE7LU= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= +github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b h1:6e93nYa3hNqAvLr0pD4PN1fFS+gKzp2zAXqrnTCstqU= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/gotest b/gotest new file mode 100755 index 0000000..38d9384 Binary files /dev/null and b/gotest differ diff --git a/lexer/lexer.go b/lexer/lexer.go index 3df1a45..7d83728 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -1,18 +1,21 @@ +// This will convert the sequence of characters into a sequence of tokens + package lexer import ( - "github.com/AvicennaJr/Nuru/token" + "github.com/NuruProgramming/Nuru/token" ) type Lexer struct { - input string // string or runes. Runes should be better. + input []rune position int readPosition int - ch byte // make this a rune too + ch rune + line int } func New(input string) *Lexer { - l := &Lexer{input: input} + l := &Lexer{input: []rune(input), line: 1} l.readChar() return l } @@ -31,79 +34,165 @@ func (l *Lexer) readChar() { func (l *Lexer) NextToken() token.Token { var tok token.Token l.skipWhitespace() - if l.ch == '/' && l.peekChar() == '/' { + if l.ch == rune('/') && l.peekChar() == rune('/') { l.skipSingleLineComment() return l.NextToken() } - if l.ch == '/' && l.peekChar() == '*' { + if l.ch == rune('/') && l.peekChar() == rune('*') { l.skipMultiLineComment() return l.NextToken() } switch l.ch { - case '=': - if l.peekChar() == '=' { + case rune('='): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.ASSIGN, l.line, l.ch) + } + case rune(';'): + tok = newToken(token.SEMICOLON, l.line, l.ch) + case rune('('): + tok = newToken(token.LPAREN, l.line, l.ch) + case rune(')'): + tok = newToken(token.RPAREN, l.line, l.ch) + case rune('{'): + tok = newToken(token.LBRACE, l.line, l.ch) + case rune('}'): + tok = newToken(token.RBRACE, l.line, l.ch) + case rune(','): + tok = newToken(token.COMMA, l.line, l.ch) + case rune('+'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == rune('+') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.PLUS_PLUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.PLUS, l.line, l.ch) + } + case rune('-'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == rune('-') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MINUS_MINUS, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.MINUS, l.line, l.ch) + } + case rune('!'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.BANG, l.line, l.ch) + } + case rune('/'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.SLASH_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.SLASH, l.line, l.ch) + } + case rune('*'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.ASTERISK_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else if l.peekChar() == rune('*') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.POW, Literal: string(ch) + string(l.ch), Line: l.line} + } else { + tok = newToken(token.ASTERISK, l.line, l.ch) + } + case rune('<'): + if l.peekChar() == rune('=') { ch := l.ch l.readChar() - tok = token.Token{Type: token.EQ, Literal: string(ch) + string(l.ch)} + tok = token.Token{Type: token.LTE, Literal: string(ch) + string(l.ch), Line: l.line} } else { - tok = newToken(token.ASSIGN, l.ch) - } - case ';': - tok = newToken(token.SEMICOLON, l.ch) - case '(': - tok = newToken(token.LPAREN, l.ch) - case ')': - tok = newToken(token.RPAREN, l.ch) - case '{': - tok = newToken(token.LBRACE, l.ch) - case '}': - tok = newToken(token.RBRACE, l.ch) - case ',': - tok = newToken(token.COMMA, l.ch) - case '+': - tok = newToken(token.PLUS, l.ch) - case '-': - tok = newToken(token.MINUS, l.ch) - case '!': - if l.peekChar() == '=' { + tok = newToken(token.LT, l.line, l.ch) + } + case rune('>'): + if l.peekChar() == rune('=') { ch := l.ch l.readChar() - tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch)} + tok = token.Token{Type: token.GTE, Literal: string(ch) + string(l.ch), Line: l.line} } else { - tok = newToken(token.BANG, l.ch) - } - case '/': - tok = newToken(token.SLASH, l.ch) - case '*': - tok = newToken(token.ASTERISK, l.ch) - case '<': - tok = newToken(token.LT, l.ch) - case '>': - tok = newToken(token.GT, l.ch) - case '"': + tok = newToken(token.GT, l.line, l.ch) + } + case rune('"'): tok.Type = token.STRING tok.Literal = l.readString() - case '[': - tok = newToken(token.LBRACKET, l.ch) - case ']': - tok = newToken(token.RBRACKET, l.ch) - case ':': - tok = newToken(token.COLON, l.ch) + tok.Line = l.line + case rune('\''): + tok = token.Token{Type: token.STRING, Literal: l.readSingleQuoteString(), Line: l.line} + case rune('['): + tok = newToken(token.LBRACKET, l.line, l.ch) + case rune(']'): + tok = newToken(token.RBRACKET, l.line, l.ch) + case rune(':'): + tok = newToken(token.COLON, l.line, l.ch) + case rune('@'): + tok = newToken(token.AT, l.line, l.ch) + case rune('.'): + tok = newToken(token.DOT, l.line, l.ch) + case rune('&'): + if l.peekChar() == rune('&') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.AND, Literal: string(ch) + string(l.ch), Line: l.line} + } + case rune('|'): + if l.peekChar() == rune('|') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.OR, Literal: string(ch) + string(l.ch), Line: l.line} + } + case rune('%'): + if l.peekChar() == rune('=') { + ch := l.ch + l.readChar() + tok = token.Token{Type: token.MODULUS_ASSIGN, Line: l.line, Literal: string(ch) + string(l.ch)} + } else { + tok = newToken(token.MODULUS, l.line, l.ch) + } + case rune('#'): + if l.peekChar() == rune('!') && l.line == 1 { + l.skipSingleLineComment() + return l.NextToken() + } case 0: tok.Literal = "" tok.Type = token.EOF + tok.Line = l.line default: if isLetter(l.ch) { tok.Literal = l.readIdentifier() tok.Type = token.LookupIdent(tok.Literal) + tok.Line = l.line + return tok + } else if isDigit(l.ch) && isLetter(l.peekChar()) { + tok.Literal = l.readIdentifier() + tok.Type = token.LookupIdent(tok.Literal) + tok.Line = l.line return tok } else if isDigit(l.ch) { - tok.Type = token.INT - tok.Literal = l.readNumber() + tok = l.readDecimal() return tok } else { - tok = newToken(token.ILLEGAL, l.ch) + tok = newToken(token.ILLEGAL, l.line, l.ch) } } @@ -111,49 +200,70 @@ func (l *Lexer) NextToken() token.Token { return tok } -func newToken(tokenType token.TokenType, ch byte) token.Token { - return token.Token{Type: tokenType, Literal: string(ch)} +func newToken(tokenType token.TokenType, line int, ch rune) token.Token { + return token.Token{Type: tokenType, Literal: string(ch), Line: line} } func (l *Lexer) readIdentifier() string { position := l.position - for isLetter(l.ch) { + for isLetter(l.ch) || isDigit(l.ch) { l.readChar() } - return l.input[position:l.position] + return string(l.input[position:l.position]) } -func isLetter(ch byte) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch == '?' || ch == '&' +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch == '@' } func (l *Lexer) skipWhitespace() { for l.ch == ' ' || l.ch == '\t' || l.ch == '\n' || l.ch == '\r' { + if l.ch == '\n' { + l.line++ + } l.readChar() } } +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' +} + func (l *Lexer) readNumber() string { position := l.position for isDigit(l.ch) { l.readChar() } - return l.input[position:l.position] + return string(l.input[position:l.position]) } -func isDigit(ch byte) bool { - return '0' <= ch && ch <= '9' +func (l *Lexer) readDecimal() token.Token { + integer := l.readNumber() + if l.ch == '.' && isDigit(l.peekChar()) { + l.readChar() + fraction := l.readNumber() + return token.Token{Type: token.FLOAT, Literal: integer + "." + fraction, Line: l.line} + } + return token.Token{Type: token.INT, Literal: integer, Line: l.line} } -func (l *Lexer) peekChar() byte { +func (l *Lexer) peekChar() rune { if l.readPosition >= len(l.input) { - return 0 + return rune(0) } else { return l.input[l.readPosition] } } +// func (l *Lexer) peekTwoChar() rune { +// if l.readPosition+1 >= len(l.input) { +// return rune(0) +// } else { +// return l.input[l.readPosition+1] +// } +// } + func (l *Lexer) skipSingleLineComment() { for l.ch != '\n' && l.ch != 0 { l.readChar() @@ -175,19 +285,67 @@ func (l *Lexer) skipMultiLineComment() { } l.readChar() + l.skipWhitespace() } - l.skipWhitespace() } func (l *Lexer) readString() string { - position := l.position + 1 + var str string for { l.readChar() if l.ch == '"' || l.ch == 0 { break + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } } + str += string(l.ch) } + return str +} - return l.input[position:l.position] +func (l *Lexer) readSingleQuoteString() string { + var str string + for { + l.readChar() + if l.ch == '\'' || l.ch == 0 { + break + } else if l.ch == '\\' { + switch l.peekChar() { + case 'n': + l.readChar() + l.ch = '\n' + case 'r': + l.readChar() + l.ch = '\r' + case 't': + l.readChar() + l.ch = '\t' + case '"': + l.readChar() + l.ch = '"' + case '\\': + l.readChar() + l.ch = '\\' + } + } + str += string(l.ch) + } + return str } diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index ccf358e..a9252bd 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -3,20 +3,20 @@ package lexer import ( "testing" - "github.com/AvicennaJr/Nuru/token" + "github.com/NuruProgramming/Nuru/token" ) func TestNextToken(t *testing.T) { input := ` // Testing kama lex luther iko sawa - acha tano = 5; - acha kumi = 10; + fanya tano = 5; + fanya kumi = 10; - acha jumla = fn(x, y){ + fanya jumla = unda(x, y){ x + y; }; - acha jibu = jumla(tano, kumi); + fanya jibu = jumla(tano, kumi); !-/5; 5 < 10 > 5; @@ -40,26 +40,41 @@ func TestNextToken(t *testing.T) { "bangi" "ba ngi" [1, 2]; - {"mambo": "vipi"}` + {"mambo": "vipi"} + . // test dot + tumia muda + + badili (a) { + ikiwa 2 { + andika(2) + } + kawaida { + andika(0) + } + } + + tupu + + kwa i, v ktk j` tests := []struct { expectedType token.TokenType expectedLiteral string }{ - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "tano"}, {token.ASSIGN, "="}, {token.INT, "5"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "kumi"}, {token.ASSIGN, "="}, {token.INT, "10"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "jumla"}, {token.ASSIGN, "="}, - {token.FUNCTION, "fn"}, + {token.FUNCTION, "unda"}, {token.LPAREN, "("}, {token.IDENT, "x"}, {token.COMMA, ","}, @@ -72,7 +87,7 @@ func TestNextToken(t *testing.T) { {token.SEMICOLON, ";"}, {token.RBRACE, "}"}, {token.SEMICOLON, ";"}, - {token.LET, "acha"}, + {token.LET, "fanya"}, {token.IDENT, "jibu"}, {token.ASSIGN, "="}, {token.IDENT, "jumla"}, @@ -132,6 +147,37 @@ func TestNextToken(t *testing.T) { {token.COLON, ":"}, {token.STRING, "vipi"}, {token.RBRACE, "}"}, + {token.DOT, "."}, + {token.IMPORT, "tumia"}, + {token.IDENT, "muda"}, + {token.SWITCH, "badili"}, + {token.LPAREN, "("}, + {token.IDENT, "a"}, + {token.RPAREN, ")"}, + {token.LBRACE, "{"}, + {token.CASE, "ikiwa"}, + {token.INT, "2"}, + {token.LBRACE, "{"}, + {token.IDENT, "andika"}, + {token.LPAREN, "("}, + {token.INT, "2"}, + {token.RPAREN, ")"}, + {token.RBRACE, "}"}, + {token.DEFAULT, "kawaida"}, + {token.LBRACE, "{"}, + {token.IDENT, "andika"}, + {token.LPAREN, "("}, + {token.INT, "0"}, + {token.RPAREN, ")"}, + {token.RBRACE, "}"}, + {token.RBRACE, "}"}, + {token.NULL, "tupu"}, + {token.FOR, "kwa"}, + {token.IDENT, "i"}, + {token.COMMA, ","}, + {token.IDENT, "v"}, + {token.IN, "ktk"}, + {token.IDENT, "j"}, {token.EOF, ""}, } diff --git a/main.go b/main.go index 54ef51a..ed36acb 100644 --- a/main.go +++ b/main.go @@ -1,58 +1,73 @@ package main import ( - "flag" "fmt" - "io/ioutil" "os" + "strings" - "github.com/AvicennaJr/Nuru/repl" + "github.com/NuruProgramming/Nuru/repl" + "github.com/NuruProgramming/Nuru/styles" + "github.com/charmbracelet/lipgloss" ) -const ( - LOGO = ` - +var ( + Title = styles.TitleStyle. + Render(` █░░ █░█ █▀▀ █░█ ▄▀█   █▄█ ▄▀█   █▄░█ █░█ █▀█ █░█ -█▄▄ █▄█ █▄█ █▀█ █▀█   ░█░ █▀█   █░▀█ █▄█ █▀▄ █▄█ - - | Authored by Avicenna | -` - VERSION = "v0.1.0" +█▄▄ █▄█ █▄█ █▀█ █▀█   ░█░ █▀█   █░▀█ █▄█ █▀▄ █▄█`) + Version = styles.VersionStyle.Render("v0.5.18") + Author = styles.AuthorStyle.Render("by Nuru Org") + NewLogo = lipgloss.JoinVertical(lipgloss.Center, Title, lipgloss.JoinHorizontal(lipgloss.Center, Author, " | ", Version)) + Help = styles.HelpStyle.Italic(false).Render(fmt.Sprintf(`💡 Namna ya kutumia Nuru: + %s: Kuanza programu ya Nuru + %s: Kuendesha faili la Nuru + %s: Kusoma nyaraka za Nuru + %s: Kufahamu toleo la Nuru +`, + styles.HelpStyle.Bold(true).Render("nuru"), + styles.HelpStyle.Bold(true).Render("nuru jinaLaFile.nr"), + styles.HelpStyle.Bold(true).Render("nuru --nyaraka"), + styles.HelpStyle.Bold(true).Render("nuru --toleo"))) ) func main() { - version := flag.Bool("v", false, "Onyesha version namba ya program") - flag.Parse() + args := os.Args + if len(args) < 2 { - if *version { - fmt.Println(fmt.Sprintf("\x1b[%dm%s%s\x1b[0m", 32, "Nuru Programming Language || Version: ", VERSION)) - os.Exit(0) + help := styles.HelpStyle.Render("💡 Tumia exit() au toka() kuondoka") + fmt.Println(lipgloss.JoinVertical(lipgloss.Left, NewLogo, "\n", help)) + repl.Start() + return } - args := flag.Args() - - if len(args) < 1 { - coloredLogo := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 34, LOGO) - fmt.Println(coloredLogo) - fmt.Println("𝑯𝒂𝒃𝒂𝒓𝒊, 𝒌𝒂𝒓𝒊𝒃𝒖 𝒖𝒕𝒖𝒎𝒊𝒆 𝒍𝒖𝒈𝒉𝒂 𝒚𝒂 𝑵𝒖𝒓𝒖 ✨") - fmt.Println("\nTumia exit() au toka() kuondoka") + if len(args) == 2 { + switch args[1] { + case "msaada", "-msaada", "--msaada", "help", "-help", "--help", "-h": + fmt.Println(Help) + case "version", "-version", "--version", "-v", "v", "--toleo", "-toleo": + fmt.Println(NewLogo) + case "-docs", "--docs", "-nyaraka", "--nyaraka": + repl.Docs() + default: + file := args[1] - repl.Start(os.Stdin, os.Stdout) - } else if len(args) == 1 { + if strings.HasSuffix(file, "nr") || strings.HasSuffix(file, ".sw") { + contents, err := os.ReadFile(file) + if err != nil { + fmt.Println(styles.ErrorStyle.Render("Error: Nuru imeshindwa kusoma faili: ", args[1])) + os.Exit(1) + } - file := args[0] - contents, err := ioutil.ReadFile(file) - if err != nil { - fmt.Println(fmt.Sprintf("\x1b[%dm%s%s\x1b[0m", 31, "Error: Nimeshindwa kusoma file: ", args[0])) - os.Exit(0) + repl.Read(string(contents)) + } else { + fmt.Println(styles.ErrorStyle.Render("'"+file+"'", "sii faili sahihi. Tumia faili la '.nr' au '.sw'")) + os.Exit(1) + } } - - repl.Read(string(contents)) - } else { - fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Error: Opereshen imeshindikana boss.")) - fmt.Println(fmt.Sprintf("\x1b[%dm%s\x1b[0m", 32, "\nTumia Command: 'nuru' kutmia program AU\nTumia Command: 'nuru' ikifuatwa na program file.\n\n\tMfano:\tnuru fileYangu.nr\n")) - os.Exit(0) + fmt.Println(styles.ErrorStyle.Render("Error: Operesheni imeshindikana boss.")) + fmt.Println(Help) + os.Exit(1) } } diff --git a/module/hisabati.go b/module/hisabati.go new file mode 100644 index 0000000..c1d84f1 --- /dev/null +++ b/module/hisabati.go @@ -0,0 +1,731 @@ +package module + +import ( + "math" + "math/rand" + "time" + + "github.com/NuruProgramming/Nuru/object" +) + +var MathFunctions = map[string]object.ModuleFunction{ + "PI": pi, + "e": e, + "phi": phi, + "ln10": ln10, + "ln2": ln2, + "log10e": log10e, + "log2e": log2e, + "log2": log2, + "sqrt1_2": sqrt1_2, + "sqrt2": sqrt2, + "sqrt3": sqrt3, + "sqrt5": sqrt5, + "EPSILON": epsilon, + "abs": abs, + "sign": sign, + "ceil": ceil, + "floor": floor, + "sqrt": sqrt, + "cbrt": cbrt, + "root": root, + "hypot": hypot, + "random": random, + "factorial": factorial, + "round": round, + "max": max, + "min": min, + "exp": exp, + "expm1": expm1, + "log": log, + "log10": log10, + "log1p": log1p, + "cos": cos, + "sin": sin, + "tan": tan, + "acos": acos, + "asin": asin, + "atan": atan, + "cosh": cosh, + "sinh": sinh, + "tanh": tanh, + "acosh": acosh, + "asinh": asinh, + "atanh": atanh, + "atan2": atan2, +} + +var Constants = map[string]object.Object{ + "PI": &object.Float{Value: math.Pi}, + "e": &object.Float{Value: math.E}, + "phi": &object.Float{Value: (1 + math.Sqrt(5)) / 2}, + "ln10": &object.Float{Value: math.Log10E}, + "ln2": &object.Float{Value: math.Ln2}, + "log10e": &object.Float{Value: math.Log10E}, + "log2e": &object.Float{Value: math.Log2E}, + "sqrt1_2": &object.Float{Value: 1 / math.Sqrt2}, + "sqrt2": &object.Float{Value: math.Sqrt2}, + "sqrt3": &object.Float{Value: math.Sqrt(3)}, + "sqrt5": &object.Float{Value: math.Sqrt(5)}, + "EPSILON": &object.Float{Value: 2.220446049250313e-16}, +} + +func pi(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Pi} +} + +func e(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.E} +} + +func phi(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: (1 + math.Sqrt(5)) / 2} +} + +func ln10(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Log10E} +} + +func ln2(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Ln2} +} + +func log10e(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Log10E} +} + +func log2e(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Log2E} +} + +func sqrt1_2(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: 1 / math.Sqrt2} +} + +func sqrt2(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Sqrt2} +} + +func sqrt3(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Sqrt(3)} +} + +func sqrt5(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: math.Sqrt(5)} +} + +func epsilon(args []object.Object, defs map[string]object.Object) object.Object { + return &object.Float{Value: 2.220446049250313e-16} +} + +func abs(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + switch arg := args[0].(type) { + case *object.Integer: + if arg.Value < 0 { + return &object.Integer{Value: -arg.Value} + } + return arg + case *object.Float: + if arg.Value < 0 { + return &object.Float{Value: -arg.Value} + } + return arg + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func sign(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + switch arg := args[0].(type) { + case *object.Integer: + if arg.Value == 0 { + return &object.Integer{Value: 0} + } else if arg.Value > 0 { + return &object.Integer{Value: 1} + } else { + return &object.Integer{Value: -1} + } + case *object.Float: + if arg.Value == 0 { + return &object.Integer{Value: 0} + } else if arg.Value > 0 { + return &object.Integer{Value: 1} + } else { + return &object.Integer{Value: -1} + } + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func ceil(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + switch arg := args[0].(type) { + case *object.Integer: + return &object.Integer{Value: arg.Value} + case *object.Float: + return &object.Integer{Value: int64(math.Ceil(arg.Value))} + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func floor(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + switch arg := args[0].(type) { + case *object.Integer: + return &object.Integer{Value: arg.Value} + case *object.Float: + return &object.Integer{Value: int64(math.Floor(arg.Value))} + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func sqrt(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + switch arg := args[0].(type) { + case *object.Integer: + return &object.Float{Value: math.Sqrt(float64(arg.Value))} + case *object.Float: + return &object.Float{Value: math.Sqrt(arg.Value)} + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func cbrt(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + switch arg := args[0].(type) { + case *object.Integer: + return &object.Float{Value: math.Cbrt(float64(arg.Value))} + case *object.Float: + return &object.Float{Value: math.Cbrt(arg.Value)} + default: + return &object.Error{Message: "Hoja lazima iwe namba"} + } +} + +func root(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 2 { + return &object.Error{Message: "Undo hili linahitaji hoja mbili tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja ya kwanza lazima iwe namba"} + } + if args[1].Type() != object.INTEGER_OBJ { + return &object.Error{Message: "Hoja ya pili lazima iwe namba"} + } + base, ok := args[0].(*object.Float) + if !ok { + base = &object.Float{Value: float64(args[0].(*object.Integer).Value)} + } + exp := args[1].(*object.Integer).Value + + if exp == 0 { + return &object.Float{Value: 1.0} + } else if exp < 0 { + return &object.Error{Message: "Second Hoja lazima iwe a non-negative integer"} + } + + x := 1.0 + for i := 0; i < 10; i++ { + x = x - (math.Pow(x, float64(exp))-base.Value)/(float64(exp)*math.Pow(x, float64(exp-1))) + } + + return &object.Float{Value: x} +} + +func hypot(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) < 2 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + var sumOfSquares float64 + for _, arg := range args { + if arg.Type() != object.INTEGER_OBJ && arg.Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima ziwe namba"} + } + switch num := arg.(type) { + case *object.Integer: + sumOfSquares += float64(num.Value) * float64(num.Value) + case *object.Float: + sumOfSquares += num.Value * num.Value + } + } + return &object.Float{Value: math.Sqrt(sumOfSquares)} +} + +func factorial(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + n := args[0].(*object.Integer).Value + if n < 0 { + return &object.Error{Message: "Hoja lazima iwe a non-negative integer"} + } + result := int64(1) + for i := int64(2); i <= n; i++ { + result *= i + } + return &object.Integer{Value: result} +} + +func round(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + + num := args[0].(*object.Float).Value + return &object.Integer{Value: int64(num + 0.5)} +} + +func max(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + + arg, ok := args[0].(*object.Array) + if !ok { + return &object.Error{Message: "Hoja lazima iwe an array"} + } + + if len(arg.Elements) == 0 { + return &object.Error{Message: "Orodha haipaswi kuwa tupu"} + } + + var maxNum float64 + + for _, element := range arg.Elements { + if element.Type() != object.INTEGER_OBJ && element.Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Vipengee vya orodha lazima viwe namba"} + } + + switch num := element.(type) { + case *object.Integer: + if float64(num.Value) > maxNum { + maxNum = float64(num.Value) + } + case *object.Float: + if num.Value > maxNum { + maxNum = num.Value + } + default: + return &object.Error{Message: "Vipengee vya orodha lazima viwe namba"} + } + } + + return &object.Float{Value: maxNum} +} + +func min(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + + arg, ok := args[0].(*object.Array) + if !ok { + return &object.Error{Message: "Hoja lazima iwe an array"} + } + + if len(arg.Elements) == 0 { + return &object.Error{Message: "Orodha haipaswi kuwa tupu"} + } + + minNum := math.MaxFloat64 + + for _, element := range arg.Elements { + if element.Type() != object.INTEGER_OBJ && element.Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Vipengee vya orodha lazima viwe namba"} + } + + switch num := element.(type) { + case *object.Integer: + if float64(num.Value) < minNum { + minNum = float64(num.Value) + } + case *object.Float: + if num.Value < minNum { + minNum = num.Value + } + default: + return &object.Error{Message: "Vipengee vya orodha lazima viwe namba"} + } + } + + return &object.Float{Value: minNum} +} + +func exp(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Exp(num)} +} + +func expm1(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Expm1(num)} +} + +func log(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Log(num)} +} + +func log10(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Log10(num)} +} + +func log2(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe namba"} + } + + arg := extractFloatValue(args[0]) + + if arg <= 0 { + return &object.Error{Message: "Hoja lazima iwe kubwa kuliko 0"} + } + + return &object.Float{Value: math.Log2(arg)} +} + +func extractFloatValue(obj object.Object) float64 { + switch obj := obj.(type) { + case *object.Integer: + return float64(obj.Value) + case *object.Float: + return obj.Value + default: + return 0 + } +} + +func log1p(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Log1p(num)} +} + +func cos(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Cos(num)} +} + +func sin(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Sin(num)} +} + +func tan(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Tan(num)} +} + +func acos(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Acos(num)} +} + +func asin(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Asin(num)} +} + +func atan(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Atan(num)} +} + +func cosh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Cosh(num)} +} + +func sinh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Sinh(num)} +} + +func tanh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Tanh(num)} +} + +func acosh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Acosh(num)} +} + +func asinh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Asinh(num)} +} + +func atan2(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 2 { + return &object.Error{Message: "Undo hili linahitaji hoja mbili tu."} + } + if args[0].Type() != object.INTEGER_OBJ && args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima ziwe namba"} + } + if args[1].Type() != object.INTEGER_OBJ && args[1].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima ziwe namba"} + } + + y := extractFloatValue(args[0]) + x := extractFloatValue(args[1]) + + return &object.Float{Value: math.Atan2(y, x)} +} + +func atanh(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + if len(args) != 1 { + return &object.Error{Message: "Undo hili linahitaji hoja moja tu"} + } + if args[0].Type() != object.FLOAT_OBJ { + return &object.Error{Message: "Hoja lazima iwe desimali"} + } + num := args[0].(*object.Float).Value + return &object.Float{Value: math.Atanh(num)} +} + +func random(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Undo hili haliruhusu ufafanuzi."} + } + + if len(args) != 0 { + return &object.Error{Message: "Undo hili halipaswi kupokea hoja."} + } + + rand.Seed(time.Now().UnixNano()) + value := rand.Float64() + + return &object.Float{Value: value} +} diff --git a/module/json.go b/module/json.go new file mode 100644 index 0000000..51a133f --- /dev/null +++ b/module/json.go @@ -0,0 +1,120 @@ +package module + +import ( + "encoding/json" + + "github.com/NuruProgramming/Nuru/object" +) + +var JsonFunctions = map[string]object.ModuleFunction{} + +func init() { + JsonFunctions["dikodi"] = decode + JsonFunctions["enkodi"] = encode +} + +func decode(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Hoja hii hairuhusiwi"} + } + if len(args) != 1 { + return &object.Error{Message: "Tunahitaji hoja moja tu"} + } + + if args[0].Type() != object.STRING_OBJ { + return &object.Error{Message: "Hoja lazima iwe neno"} + } + + var i interface{} + + input := args[0].(*object.String).Value + err := json.Unmarshal([]byte(input), &i) + if err != nil { + return &object.Error{Message: "Hii data sio jsoni"} + } + + return convertWhateverToObject(i) +} + +func convertWhateverToObject(i interface{}) object.Object { + switch v := i.(type) { + case map[string]interface{}: + dict := &object.Dict{} + dict.Pairs = make(map[object.HashKey]object.DictPair) + + for k, v := range v { + pair := object.DictPair{ + Key: &object.String{Value: k}, + Value: convertWhateverToObject(v), + } + dict.Pairs[pair.Key.(object.Hashable).HashKey()] = pair + } + + return dict + case []interface{}: + list := &object.Array{} + for _, e := range v { + list.Elements = append(list.Elements, convertWhateverToObject(e)) + } + + return list + case string: + return &object.String{Value: v} + case int64: + return &object.Integer{Value: v} + case float64: + return &object.Float{Value: v} + case bool: + if v { + return &object.Boolean{Value: true} + } else { + return &object.Boolean{Value: false} + } + } + return &object.Null{} +} + +func encode(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Hoja hii hairuhusiwi"} + } + + input := args[0] + i := convertObjectToWhatever(input) + data, err := json.Marshal(i) + + if err != nil { + return &object.Error{Message: "Siwezi kubadilisha data hii kuwa jsoni"} + } + + return &object.String{Value: string(data)} +} + +func convertObjectToWhatever(obj object.Object) interface{} { + switch v := obj.(type) { + case *object.Dict: + m := make(map[string]interface{}) + for _, pair := range v.Pairs { + key := pair.Key.(*object.String).Value + m[key] = convertObjectToWhatever(pair.Value) + } + return m + case *object.Array: + list := make([]interface{}, len(v.Elements)) + for i, e := range v.Elements { + list[i] = convertObjectToWhatever(e) + } + return list + case *object.String: + return v.Value + case *object.Integer: + return v.Value + case *object.Float: + return v.Value + case *object.Boolean: + return v.Value + case *object.Null: + return nil + } + return nil +} diff --git a/module/module.go b/module/module.go new file mode 100644 index 0000000..f4c17c7 --- /dev/null +++ b/module/module.go @@ -0,0 +1,13 @@ +package module + +import "github.com/NuruProgramming/Nuru/object" + +var Mapper = map[string]*object.Module{} + +func init() { + Mapper["os"] = &object.Module{Name: "os", Functions: OsFunctions} + Mapper["muda"] = &object.Module{Name: "time", Functions: TimeFunctions} + Mapper["mtandao"] = &object.Module{Name: "net", Functions: NetFunctions} + Mapper["jsoni"] = &object.Module{Name: "json", Functions: JsonFunctions} + Mapper["hisabati"] = &object.Module{Name: "hisabati", Functions: MathFunctions} +} diff --git a/module/net.go b/module/net.go new file mode 100644 index 0000000..421d67b --- /dev/null +++ b/module/net.go @@ -0,0 +1,201 @@ +package module + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/NuruProgramming/Nuru/object" +) + +var NetFunctions = map[string]object.ModuleFunction{} + +func init() { + NetFunctions["peruzi"] = getRequest + NetFunctions["tuma"] = postRequest +} + +func getRequest(args []object.Object, defs map[string]object.Object) object.Object { + + if len(defs) != 0 { + var url *object.String + var headers, params *object.Dict + for k, v := range defs { + switch k { + case "yuareli": + strUrl, ok := v.(*object.String) + if !ok { + return &object.Error{Message: "Yuareli iwe neno"} + } + url = strUrl + case "vichwa": + dictHead, ok := v.(*object.Dict) + if !ok { + return &object.Error{Message: "Vichwa lazima viwe kamusi"} + } + headers = dictHead + case "mwili": + dictHead, ok := v.(*object.Dict) + if !ok { + return &object.Error{Message: "Mwili lazima iwe kamusi"} + } + params = dictHead + default: + return &object.Error{Message: "Hoja si sahihi. Tumia yuareli na vichwa."} + } + } + if url.Value == "" { + return &object.Error{Message: "Yuareli ni lazima"} + } + + var responseBody *bytes.Buffer + if params != nil { + booty := convertObjectToWhatever(params) + + jsonBody, err := json.Marshal(booty) + + if err != nil { + return &object.Error{Message: "Huku format query yako vizuri."} + } + + responseBody = bytes.NewBuffer(jsonBody) + } + + var req *http.Request + var err error + if responseBody != nil { + req, err = http.NewRequest("GET", url.Value, responseBody) + } else { + req, err = http.NewRequest("GET", url.Value, nil) + } + if err != nil { + return &object.Error{Message: "Tumeshindwa kufanya request"} + } + + if headers != nil { + for _, val := range headers.Pairs { + req.Header.Set(val.Key.Inspect(), val.Value.Inspect()) + } + } + client := &http.Client{} + + resp, err := client.Do(req) + + if err != nil { + return &object.Error{Message: "Tumeshindwa kutuma request."} + } + defer resp.Body.Close() + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &object.Error{Message: "Tumeshindwa kusoma majibu."} + } + + return &object.String{Value: string(respBody)} + + } + + if len(args) == 1 { + url, ok := args[0].(*object.String) + if !ok { + return &object.Error{Message: "Yuareli lazima iwe neno"} + } + req, err := http.NewRequest("GET", url.Value, nil) + if err != nil { + return &object.Error{Message: "Tumeshindwa kufanya request"} + } + + client := &http.Client{} + + resp, err := client.Do(req) + + if err != nil { + return &object.Error{Message: "Tumeshindwa kutuma request."} + } + defer resp.Body.Close() + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &object.Error{Message: "Tumeshindwa kusoma majibu."} + } + + return &object.String{Value: string(respBody)} + } + return &object.Error{Message: "Hoja si sahihi. Tumia yuareli na vichwa."} +} + +func postRequest(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + var url *object.String + var headers, params *object.Dict + for k, v := range defs { + switch k { + case "yuareli": + strUrl, ok := v.(*object.String) + if !ok { + return &object.Error{Message: "Yuareli iwe neno"} + } + url = strUrl + case "vichwa": + dictHead, ok := v.(*object.Dict) + if !ok { + return &object.Error{Message: "Vichwa lazima viwe kamusi"} + } + headers = dictHead + case "mwili": + dictHead, ok := v.(*object.Dict) + if !ok { + return &object.Error{Message: "Mwili lazima iwe kamusi"} + } + params = dictHead + default: + return &object.Error{Message: "Hoja si sahihi. Tumia yuareli na vichwa."} + } + } + if url.Value == "" { + return &object.Error{Message: "Yuareli ni lazima"} + } + var responseBody *bytes.Buffer + if params != nil { + booty := convertObjectToWhatever(params) + + jsonBody, err := json.Marshal(booty) + + if err != nil { + return &object.Error{Message: "Huku format query yako vizuri."} + } + + responseBody = bytes.NewBuffer(jsonBody) + } + var req *http.Request + var err error + if responseBody != nil { + req, err = http.NewRequest("POST", url.Value, responseBody) + } else { + req, err = http.NewRequest("POST", url.Value, nil) + } + if err != nil { + return &object.Error{Message: "Tumeshindwa kufanya request"} + } + if headers != nil { + for _, val := range headers.Pairs { + req.Header.Set(val.Key.Inspect(), val.Value.Inspect()) + } + } + req.Header.Add("Content-Type", "application/json") + + client := &http.Client{} + + resp, err := client.Do(req) + + if err != nil { + return &object.Error{Message: "Tumeshindwa kutuma request."} + } + defer resp.Body.Close() + respBody, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &object.Error{Message: "Tumeshindwa kusoma majibu."} + } + return &object.String{Value: string(respBody)} + } + return &object.Error{Message: "Hoja si sahihi. Tumia yuareli na vichwa."} +} diff --git a/module/os.go b/module/os.go new file mode 100644 index 0000000..53194f4 --- /dev/null +++ b/module/os.go @@ -0,0 +1,56 @@ +package module + +import ( + "os" + "os/exec" + "strings" + + "github.com/NuruProgramming/Nuru/object" +) + +var OsFunctions = map[string]object.ModuleFunction{} + +func init() { + OsFunctions["toka"] = exit + OsFunctions["kimbiza"] = run +} + +func exit(args []object.Object, defs map[string]object.Object) object.Object { + if len(args) > 1 { + return &object.Error{Message: "Hoja sii sahihi"} + } + + if len(args) == 1 { + status, ok := args[0].(*object.Integer) + if !ok { + return &object.Error{Message: "Hoja sii namba"} + } + os.Exit(int(status.Value)) + return nil + } + + os.Exit(0) + + return nil +} + +func run(args []object.Object, defs map[string]object.Object) object.Object { + if len(args) != 1 { + return &object.Error{Message: "Idadi ya hoja sii sahihi"} + } + + cmd, ok := args[0].(*object.String) + if !ok { + return &object.Error{Message: "Hoja lazima iwe neno"} + } + cmdMain := cmd.Value + cmdArgs := strings.Split(cmdMain, " ") + cmdArgs = cmdArgs[1:] + + out, err := exec.Command(cmdMain, cmdArgs...).Output() + if err != nil { + return &object.Error{Message: "Tumeshindwa kukimbiza komandi"} + } + + return &object.String{Value: string(out)} +} diff --git a/module/time.go b/module/time.go new file mode 100644 index 0000000..a4b3258 --- /dev/null +++ b/module/time.go @@ -0,0 +1,191 @@ +package module + +import ( + "fmt" + "strconv" + "time" + + "github.com/NuruProgramming/Nuru/object" +) + +var TimeFunctions = map[string]object.ModuleFunction{} + +func init() { + TimeFunctions["hasahivi"] = now + TimeFunctions["lala"] = sleep + TimeFunctions["tangu"] = since + TimeFunctions["leo"] = today + TimeFunctions["baada_ya"] = after + TimeFunctions["tofauti"] = diff + TimeFunctions["ongeza"] = addTime +} + +func now(args []object.Object, defs map[string]object.Object) object.Object { + if len(args) != 0 || len(defs) != 0 { + return &object.Error{Message: "hatuhitaji hoja kwenye hasahivi"} + } + + tn := time.Now() + time_string := tn.Format("15:04:05 02-01-2006") + + return &object.Time{TimeValue: time_string} +} + +func sleep(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Hoja hii hairuhusiwi"} + } + if len(args) != 1 { + return &object.Error{Message: "tunahitaji hoja moja tu"} + } + + objvalue := args[0].Inspect() + inttime, err := strconv.Atoi(objvalue) + + if err != nil { + return &object.Error{Message: "namba tu zinaruhusiwa kwenye hoja"} + } + + time.Sleep(time.Duration(inttime) * time.Second) + + return nil +} + +func since(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 { + return &object.Error{Message: "Hoja hii hairuhusiwi"} + } + if len(args) != 1 { + return &object.Error{Message: "tunahitaji hoja moja tu"} + } + + var ( + t time.Time + err error + ) + + switch m := args[0].(type) { + case *object.Time: + t, _ = time.Parse("15:04:05 02-01-2006", m.TimeValue) + case *object.String: + t, err = time.Parse("15:04:05 02-01-2006", m.Value) + if err != nil { + return &object.Error{Message: fmt.Sprintf("Hoja %s sii sahihi", args[0].Inspect())} + } + default: + return &object.Error{Message: fmt.Sprintf("Hoja %s sii sahihi", args[0].Inspect())} + } + + current_time := time.Now().Format("15:04:05 02-01-2006") + ct, _ := time.Parse("15:04:05 02-01-2006", current_time) + + diff := ct.Sub(t) + durationInSeconds := diff.Seconds() + + return &object.Integer{Value: int64(durationInSeconds)} +} + +func today(args []object.Object, defs map[string]object.Object) object.Object { + if len(args) != 0 || len(defs) != 0 { + return &object.Error{Message: "hatuhitaji hoja kwenye leo"} + } + + dateStr := time.Now().Format("02-01-2006") + return &object.String{Value: dateStr} +} + +func after(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 || len(args) != 1 { + return &object.Error{Message: "tunahitaji hoja moja tu kwenye baada_ya"} + } + + secondsStr := args[0].Inspect() + seconds, err := strconv.Atoi(secondsStr) + if err != nil { + return &object.Error{Message: "hoja lazima iwe namba"} + } + + future := time.Now().Add(time.Duration(seconds) * time.Second) + return &object.Time{TimeValue: future.Format("15:04:05 02-01-2006")} +} + +func diff(args []object.Object, defs map[string]object.Object) object.Object { + if len(defs) != 0 || len(args) != 2 { + return &object.Error{Message: "tunahitaji hoja mbili kwenye tofauti"} + } + + parseTime := func(o object.Object) (time.Time, error) { + switch v := o.(type) { + case *object.Time: + return time.Parse("15:04:05 02-01-2006", v.TimeValue) + case *object.String: + return time.Parse("15:04:05 02-01-2006", v.Value) + default: + return time.Time{}, fmt.Errorf("aina batili") + } + } + + t1, err1 := parseTime(args[0]) + t2, err2 := parseTime(args[1]) + + if err1 != nil || err2 != nil { + return &object.Error{Message: "tofauti inahitaji nyakati halali mbili"} + } + + diff := t1.Sub(t2).Seconds() + return &object.Integer{Value: int64(diff)} +} + + +func addTime(args []object.Object, defs map[string]object.Object) object.Object { + if len(args) != 1 { + return &object.Error{Message: "ongeza inahitaji wakati mmoja wa kuanzia"} + } + + baseTimeObj := args[0] + baseTime, err := func() (time.Time, error) { + switch t := baseTimeObj.(type) { + case *object.Time: + return time.Parse("15:04:05 02-01-2006", t.TimeValue) + case *object.String: + return time.Parse("15:04:05 02-01-2006", t.Value) + default: + return time.Time{}, fmt.Errorf("aina ya wakati sio sahihi") + } + }() + if err != nil { + return &object.Error{Message: "wakati uliotolewa sio sahihi"} + } + + secs := getInt(defs["sekunde"]) + mins := getInt(defs["dakika"]) + hours := getInt(defs["masaa"]) + days := getInt(defs["siku"]) + weeks := getInt(defs["wiki"]) + months := getInt(defs["miezi"]) + years := getInt(defs["miaka"]) + + result := baseTime. + Add(time.Second * time.Duration(secs)). + Add(time.Minute * time.Duration(mins)). + Add(time.Hour * time.Duration(hours)). + AddDate(years, months, days+(weeks*7)) + + return &object.Time{TimeValue: result.Format("15:04:05 02-01-2006")} +} + +func getInt(obj object.Object) int { + if obj == nil { + return 0 + } + switch o := obj.(type) { + case *object.Integer: + return int(o.Value) + case *object.String: + n, err := strconv.Atoi(o.Value) + if err == nil { + return n + } + } + return 0 +} diff --git a/object/array.go b/object/array.go new file mode 100644 index 0000000..6bdc41d --- /dev/null +++ b/object/array.go @@ -0,0 +1,131 @@ +package object + +import ( + "bytes" + "strings" +) + +type Array struct { + Elements []Object + offset int +} + +func (ao *Array) Type() ObjectType { return ARRAY_OBJ } +func (ao *Array) Inspect() string { + var out bytes.Buffer + + elements := []string{} + if len(ao.Elements) != 0 { + for _, e := range ao.Elements { + if e.Inspect() != "" { + elements = append(elements, e.Inspect()) + } + } + } + + out.WriteString("[") + out.WriteString(strings.Join(elements, ", ")) + out.WriteString("]") + + return out.String() +} + +func (ao *Array) Next() (Object, Object) { + idx := ao.offset + if len(ao.Elements) > idx { + ao.offset = idx + 1 + return &Integer{Value: int64(idx)}, ao.Elements[idx] + } + return nil, nil +} + +func (ao *Array) Reset() { + ao.offset = 0 +} + +func (a *Array) Method(method string, args []Object) Object { + switch method { + case "idadi": + return a.len(args) + case "sukuma": + return a.push(args) + case "yamwisho": + return a.last() + case "unga": + return a.join(args) + case "chuja": + return a.filter(args) + case "tafuta": + return a.find(args) + default: + return newError("Samahani, kiendesha hiki hakitumiki na tungo (Neno)") + } +} + +func (a *Array) len(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &Integer{Value: int64(len(a.Elements))} +} + +func (a *Array) last() Object { + length := len(a.Elements) + if length > 0 { + return a.Elements[length-1] + } + return &Null{} +} + +func (a *Array) push(args []Object) Object { + a.Elements = append(a.Elements, args...) + return a +} + +func (a *Array) join(args []Object) Object { + if len(args) > 1 { + return newError("Samahani, tunahitaji Hoja 1 au 0, wewe umeweka %d", len(args)) + } + if len(a.Elements) > 0 { + glue := "" + if len(args) == 1 { + glue = args[0].(*String).Value + } + length := len(a.Elements) + newElements := make([]string, length) + for k, v := range a.Elements { + newElements[k] = v.Inspect() + } + return &String{Value: strings.Join(newElements, glue)} + } else { + return &String{Value: ""} + } +} + +func (a *Array) filter(args []Object) Object { + if len(args) != 1 { + return newError("Samahani, idadi ya hoja sii sahihi") + } + + dummy := []Object{} + filteredArr := Array{Elements: dummy} + for _, obj := range a.Elements { + if obj.Inspect() == args[0].Inspect() && obj.Type() == args[0].Type() { + filteredArr.Elements = append(filteredArr.Elements, obj) + } + } + return &filteredArr +} + +func (a *Array) find(args []Object) Object { + if len(args) != 1 { + return newError("Samahani, idadi ya hoja sii sahihi") + } + + for _, obj := range a.Elements { + if obj.Inspect() == args[0].Inspect() && obj.Type() == args[0].Type() { + return obj + } + } + return &Null{} +} diff --git a/object/at.go b/object/at.go new file mode 100644 index 0000000..7a56233 --- /dev/null +++ b/object/at.go @@ -0,0 +1,12 @@ +package object + +import "fmt" + +type At struct { + Instance *Instance +} + +func (a *At) Type() ObjectType { return AT } +func (a *At) Inspect() string { + return fmt.Sprintf("@.%s", a.Instance.Package.Name.Value) +} diff --git a/object/bool.go b/object/bool.go new file mode 100644 index 0000000..8702d52 --- /dev/null +++ b/object/bool.go @@ -0,0 +1,26 @@ +package object + +type Boolean struct { + Value bool +} + +func (b *Boolean) Inspect() string { + if b.Value { + return "kweli" + } else { + return "sikweli" + } +} +func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } + +func (b *Boolean) HashKey() HashKey { + var value uint64 + + if b.Value { + value = 1 + } else { + value = 0 + } + + return HashKey{Type: b.Type(), Value: value} +} diff --git a/object/break.go b/object/break.go new file mode 100644 index 0000000..047b17c --- /dev/null +++ b/object/break.go @@ -0,0 +1,6 @@ +package object + +type Break struct{} + +func (b *Break) Type() ObjectType { return BREAK_OBJ } +func (b *Break) Inspect() string { return "break" } diff --git a/object/builtin.go b/object/builtin.go new file mode 100644 index 0000000..b47c051 --- /dev/null +++ b/object/builtin.go @@ -0,0 +1,10 @@ +package object + +type BuiltinFunction func(args ...Object) Object + +type Builtin struct { + Fn BuiltinFunction +} + +func (b *Builtin) Inspect() string { return "builtin function" } +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } diff --git a/object/byte.go b/object/byte.go new file mode 100644 index 0000000..b5353e2 --- /dev/null +++ b/object/byte.go @@ -0,0 +1,9 @@ +package object + +type Byte struct { + Value []byte + String string +} + +func (b *Byte) Inspect() string { return "b" + b.String } +func (b *Byte) Type() ObjectType { return BYTE_OBJ } diff --git a/object/continue.go b/object/continue.go new file mode 100644 index 0000000..15c7355 --- /dev/null +++ b/object/continue.go @@ -0,0 +1,6 @@ +package object + +type Continue struct{} + +func (c *Continue) Type() ObjectType { return CONTINUE_OBJ } +func (c *Continue) Inspect() string { return "continue" } diff --git a/object/dict.go b/object/dict.go new file mode 100644 index 0000000..86ca482 --- /dev/null +++ b/object/dict.go @@ -0,0 +1,60 @@ +package object + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +type DictPair struct { + Key Object + Value Object +} + +type Dict struct { + Pairs map[HashKey]DictPair + offset int +} + +func (d *Dict) Type() ObjectType { return DICT_OBJ } +func (d *Dict) Inspect() string { + var out bytes.Buffer + + pairs := []string{} + + for _, pair := range d.Pairs { + pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) + } + + out.WriteString("{") + out.WriteString(strings.Join(pairs, ", ")) + out.WriteString("}") + + return out.String() +} + +func (d *Dict) Next() (Object, Object) { + idx := 0 + dict := make(map[string]DictPair) + var keys []string + for _, v := range d.Pairs { + dict[v.Key.Inspect()] = v + keys = append(keys, v.Key.Inspect()) + } + + sort.Strings(keys) + + for _, k := range keys { + if d.offset == idx { + d.offset += 1 + return dict[k].Key, dict[k].Value + } + idx += 1 + } + return nil, nil +} + +func (d *Dict) Reset() { + d.offset = 0 +} diff --git a/object/environment.go b/object/environment.go index 6f876e8..453f8cc 100644 --- a/object/environment.go +++ b/object/environment.go @@ -29,3 +29,11 @@ func (e *Environment) Set(name string, val Object) Object { e.store[name] = val return val } + +func (e *Environment) Del(name string) bool { + _, ok := e.store[name] + if ok { + delete(e.store, name) + } + return true +} diff --git a/object/error.go b/object/error.go new file mode 100644 index 0000000..2bc65af --- /dev/null +++ b/object/error.go @@ -0,0 +1,13 @@ +package object + +import "fmt" + +type Error struct { + Message string +} + +func (e *Error) Inspect() string { + msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "Kosa: ") + return msg + e.Message +} +func (e *Error) Type() ObjectType { return ERROR_OBJ } diff --git a/object/file.go b/object/file.go new file mode 100644 index 0000000..a502c9f --- /dev/null +++ b/object/file.go @@ -0,0 +1,68 @@ +package object + +import ( + "os" +) + +type File struct { + Filename string + Content string +} + +func (f *File) Type() ObjectType { return FILE_OBJ } +func (f *File) Inspect() string { return f.Filename } +func (f *File) Method(method string, args []Object) Object { + switch method { + case "soma": + return f.read(args) + case "andika": + return f.write(args) + case "ongeza": + return f.append(args) + } + return nil +} + +func (f *File) read(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &String{Value: f.Content} +} + +func (f *File) write(args []Object) Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) + } + content, ok := args[0].(*String) + if !ok { + return newError("Samahani, hoja lazima iwe Tungo") + } + err := os.WriteFile(f.Filename, []byte(content.Value), 0644) + if err != nil { + return newError("Hitilafu katika kuandika faili: %s", err.Error()) + } + f.Content = content.Value + return &Boolean{Value: true} +} + +func (f *File) append(args []Object) Object { + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) + } + content, ok := args[0].(*String) + if !ok { + return newError("Samahani, hoja lazima iwe Tungo") + } + file, err := os.OpenFile(f.Filename, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return newError("Hitilafu katika kufungua faili: %s", err.Error()) + } + defer file.Close() + _, err = file.WriteString(content.Value) + if err != nil { + return newError("Hitilafu katika kuongeza kwa faili: %s", err.Error()) + } + f.Content += content.Value + return &Boolean{Value: true} +} diff --git a/object/float.go b/object/float.go new file mode 100644 index 0000000..a4b2293 --- /dev/null +++ b/object/float.go @@ -0,0 +1,19 @@ +package object + +import ( + "hash/fnv" + "strconv" +) + +type Float struct { + Value float64 +} + +func (f *Float) Inspect() string { return strconv.FormatFloat(f.Value, 'f', -1, 64) } +func (f *Float) Type() ObjectType { return FLOAT_OBJ } + +func (f *Float) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(f.Inspect())) + return HashKey{Type: f.Type(), Value: h.Sum64()} +} diff --git a/object/function.go b/object/function.go new file mode 100644 index 0000000..f7ad6b8 --- /dev/null +++ b/object/function.go @@ -0,0 +1,35 @@ +package object + +import ( + "bytes" + "strings" + + "github.com/NuruProgramming/Nuru/ast" +) + +type Function struct { + Name string + Parameters []*ast.Identifier + Defaults map[string]ast.Expression + Body *ast.BlockStatement + Env *Environment +} + +func (f *Function) Type() ObjectType { return FUNCTION_OBJ } +func (f *Function) Inspect() string { + var out bytes.Buffer + + params := []string{} + for _, p := range f.Parameters { + params = append(params, p.String()) + } + + out.WriteString("unda") + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") {\n") + out.WriteString(f.Body.String()) + out.WriteString("\n}") + + return out.String() +} diff --git a/object/instance.go b/object/instance.go new file mode 100644 index 0000000..4a005a6 --- /dev/null +++ b/object/instance.go @@ -0,0 +1,13 @@ +package object + +import "fmt" + +type Instance struct { + Package *Package + Env *Environment +} + +func (i *Instance) Type() ObjectType { return INSTANCE } +func (i *Instance) Inspect() string { + return fmt.Sprintf("Pakeji: %s", i.Package.Name.Value) +} diff --git a/object/integer.go b/object/integer.go new file mode 100644 index 0000000..87cb945 --- /dev/null +++ b/object/integer.go @@ -0,0 +1,14 @@ +package object + +import "fmt" + +type Integer struct { + Value int64 +} + +func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } +func (i *Integer) Type() ObjectType { return INTEGER_OBJ } + +func (i *Integer) HashKey() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} diff --git a/object/module.go b/object/module.go new file mode 100644 index 0000000..127c741 --- /dev/null +++ b/object/module.go @@ -0,0 +1,20 @@ +package object + +type ModuleFunction func(args []Object, defs map[string]Object) Object + +type Module struct { + Name string + Functions map[string]ModuleFunction +} + +func (m *Module) Type() ObjectType { + switch m.Name { + case "time": + return TIME_OBJ + case "json": + return JSON_OBJ + default: + return MODULE_OBJ + } +} +func (m *Module) Inspect() string { return "Module: " + m.Name } diff --git a/object/null.go b/object/null.go new file mode 100644 index 0000000..1610994 --- /dev/null +++ b/object/null.go @@ -0,0 +1,6 @@ +package object + +type Null struct{} + +func (n *Null) Inspect() string { return "null" } +func (n *Null) Type() ObjectType { return NULL_OBJ } diff --git a/object/object.go b/object/object.go index fba6726..46de338 100644 --- a/object/object.go +++ b/object/object.go @@ -1,27 +1,33 @@ package object import ( - "bytes" "fmt" - "hash/fnv" - "strings" - - "github.com/AvicennaJr/Nuru/ast" ) type ObjectType string const ( INTEGER_OBJ = "NAMBA" + FLOAT_OBJ = "DESIMALI" BOOLEAN_OBJ = "BOOLEAN" - NULL_OBJ = "NULL" + NULL_OBJ = "TUPU" RETURN_VALUE_OBJ = "RUDISHA" ERROR_OBJ = "KOSA" - FUNCTION_OBJ = "FUNCTION" + FUNCTION_OBJ = "UNDO (FUNCTION)" STRING_OBJ = "NENO" BUILTIN_OBJ = "YA_NDANI" - ARRAY_OBJ = "ARRAY" + ARRAY_OBJ = "ORODHA" DICT_OBJ = "KAMUSI" + CONTINUE_OBJ = "ENDELEA" + BREAK_OBJ = "VUNJA" + FILE_OBJ = "FAILI" + TIME_OBJ = "MUDA" + JSON_OBJ = "JSONI" + MODULE_OBJ = "MODULE" + BYTE_OBJ = "BYTE" + PACKAGE_OBJ = "PAKEJI" + INSTANCE = "PAKEJI" + AT = "@" ) type Object interface { @@ -29,163 +35,22 @@ type Object interface { Inspect() string } -type Integer struct { - Value int64 -} - -func (i *Integer) Inspect() string { return fmt.Sprintf("%d", i.Value) } -func (i *Integer) Type() ObjectType { return INTEGER_OBJ } - -type Boolean struct { - Value bool -} - -func (b *Boolean) Inspect() string { - if b.Value { - return "kweli" - } else { - return "sikweli" - } -} -func (b *Boolean) Type() ObjectType { return BOOLEAN_OBJ } - -type Null struct{} - -func (n *Null) Inspect() string { return "null" } -func (n *Null) Type() ObjectType { return NULL_OBJ } - -type ReturnValue struct { - Value Object -} - -func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } -func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } - -type Error struct { - Message string -} - -func (e *Error) Inspect() string { - msg := fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, "ERROR: ") - return msg + e.Message -} -func (e *Error) Type() ObjectType { return ERROR_OBJ } - -type Function struct { - Parameters []*ast.Identifier - Body *ast.BlockStatement - Env *Environment -} - -func (f *Function) Type() ObjectType { return FUNCTION_OBJ } -func (f *Function) Inspect() string { - var out bytes.Buffer - - params := []string{} - for _, p := range f.Parameters { - params = append(params, p.String()) - } - - out.WriteString("fn") - out.WriteString("(") - out.WriteString(strings.Join(params, ", ")) - out.WriteString(") {\n") - out.WriteString(f.Body.String()) - out.WriteString("\n}") - - return out.String() -} - -type String struct { - Value string -} - -func (s *String) Inspect() string { return s.Value } -func (s *String) Type() ObjectType { return STRING_OBJ } - -type BuiltinFunction func(args ...Object) Object - -type Builtin struct { - Fn BuiltinFunction -} - -func (b *Builtin) Inspect() string { return "builtin function" } -func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } - -type Array struct { - Elements []Object -} - -func (ao *Array) Type() ObjectType { return ARRAY_OBJ } -func (ao *Array) Inspect() string { - var out bytes.Buffer - - elements := []string{} - for _, e := range ao.Elements { - elements = append(elements, e.Inspect()) - } - - out.WriteString("[") - out.WriteString(strings.Join(elements, ", ")) - out.WriteString("]") - - return out.String() -} - type HashKey struct { Type ObjectType Value uint64 } -func (b *Boolean) HashKey() HashKey { - var value uint64 - - if b.Value { - value = 1 - } else { - value = 0 - } - - return HashKey{Type: b.Type(), Value: value} -} - -func (i *Integer) HashKey() HashKey { - return HashKey{Type: i.Type(), Value: uint64(i.Value)} -} - -func (s *String) HashKey() HashKey { - h := fnv.New64a() - h.Write([]byte(s.Value)) - - return HashKey{Type: s.Type(), Value: h.Sum64()} -} - -type DictPair struct { - Key Object - Value Object -} - -type Dict struct { - Pairs map[HashKey]DictPair +type Hashable interface { + HashKey() HashKey } -func (d *Dict) Type() ObjectType { return DICT_OBJ } -func (d *Dict) Inspect() string { - var out bytes.Buffer - - pairs := []string{} - - for _, pair := range d.Pairs { - pairs = append(pairs, fmt.Sprintf("%s: %s", pair.Key.Inspect(), pair.Value.Inspect())) - } - - out.WriteString("{") - out.WriteString(strings.Join(pairs, ", ")) - out.WriteString("}") - - return out.String() +// Iterable interface for dicts, strings and arrays +type Iterable interface { + Next() (Object, Object) + Reset() } -type Hashable interface { - HashKey() HashKey +func newError(format string, a ...interface{}) *Error { + format = fmt.Sprintf("\x1b[%dm%s\x1b[0m", 31, format) + return &Error{Message: fmt.Sprintf(format, a...)} } diff --git a/object/package.go b/object/package.go new file mode 100644 index 0000000..3b104b5 --- /dev/null +++ b/object/package.go @@ -0,0 +1,18 @@ +package object + +import ( + "fmt" + + "github.com/NuruProgramming/Nuru/ast" +) + +type Package struct { + Name *ast.Identifier + Env *Environment + Scope *Environment +} + +func (p *Package) Type() ObjectType { return PACKAGE_OBJ } +func (p *Package) Inspect() string { + return fmt.Sprintf("Pakeji: %s", p.Name.Value) +} diff --git a/object/return.go b/object/return.go new file mode 100644 index 0000000..42c7225 --- /dev/null +++ b/object/return.go @@ -0,0 +1,8 @@ +package object + +type ReturnValue struct { + Value Object +} + +func (rv *ReturnValue) Inspect() string { return rv.Value.Inspect() } +func (rv *ReturnValue) Type() ObjectType { return RETURN_VALUE_OBJ } diff --git a/object/strings.go b/object/strings.go new file mode 100644 index 0000000..095a832 --- /dev/null +++ b/object/strings.go @@ -0,0 +1,177 @@ +package object + +import ( + "fmt" + "hash/fnv" + "strconv" + "strings" +) + +type String struct { + Value string + offset int +} + +func (s *String) Inspect() string { return s.Value } +func (s *String) Type() ObjectType { return STRING_OBJ } +func (s *String) HashKey() HashKey { + h := fnv.New64a() + h.Write([]byte(s.Value)) + + return HashKey{Type: s.Type(), Value: h.Sum64()} +} +func (s *String) Next() (Object, Object) { + offset := s.offset + if len(s.Value) > offset { + s.offset = offset + 1 + return &Integer{Value: int64(offset)}, &String{Value: string(s.Value[offset])} + } + return nil, nil +} +func (s *String) Reset() { + s.offset = 0 +} +func (s *String) Method(method string, args []Object) Object { + switch method { + case "idadi": + return s.len(args) + case "herufikubwa": + return s.upper(args) + case "herufindogo": + return s.lower(args) + case "gawa": + return s.split(args) + case "panga": + return s.format(args) + default: + return newError("Samahani, kiendesha hiki hakitumiki na tungo (Neno)") + } +} + +func (s *String) len(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &Integer{Value: int64(len(s.Value))} +} + +func (s *String) upper(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &String{Value: strings.ToUpper(s.Value)} +} + +func (s *String) lower(args []Object) Object { + if len(args) != 0 { + return newError("Samahani, tunahitaji Hoja 0, wewe umeweka %d", len(args)) + } + return &String{Value: strings.ToLower(s.Value)} +} + +func (s *String) split(args []Object) Object { + if len(args) > 1 { + return newError("Samahani, tunahitaji Hoja 1 au 0, wewe umeweka %d", len(args)) + } + sep := " " + if len(args) == 1 { + sep = args[0].(*String).Value + } + parts := strings.Split(s.Value, sep) + length := len(parts) + elements := make([]Object, length) + for k, v := range parts { + elements[k] = &String{Value: v} + } + return &Array{Elements: elements} +} + +func (s *String) format(args []Object) Object { + value, err := formatStr(s.Value, args) + + if err != nil { + return newError(err.Error()) + } + + return &String{Value: value} +} + +func formatStr(format string, options []Object) (string, error) { + var str strings.Builder + var val strings.Builder + var check_val bool + var opts_len int = len(options) + + var escapeChar bool + + type optM struct { + val bool + obj Object + } + + var optionsMap = make(map[int]optM, opts_len) + + for i, optm := range options { + optionsMap[i] = optM{val: false, obj: optm} + } + + for _, opt := range format { + + if !escapeChar && opt == '\\' { + escapeChar = true + continue + } + + if opt == '{' && !escapeChar { + check_val = true + continue + } + + if escapeChar { + if opt != '{' && opt != '}' { + str.WriteRune('\\') + } + escapeChar = false + } + + if check_val && opt == '}' { + vstr := strings.TrimSpace(val.String()) + arrv, err := strconv.Atoi(vstr) + if err != nil { + return "", fmt.Errorf(fmt.Sprintf("Ulichopeana si NAMBA, jaribu tena: `%s'", vstr)) + } + + oVal, exists := optionsMap[arrv] + + if !exists { + return "", fmt.Errorf(fmt.Sprintf("Nambari ya chaguo unalolitaka %d ni kubwa kuliko ulizopeana (%d)", arrv, opts_len)) + } + + str.WriteString(oVal.obj.Inspect()) + optionsMap[arrv] = optM{val: true, obj: oVal.obj} + + check_val = false + val.Reset() + continue + } + + if check_val { + val.WriteRune(opt) + continue + } + + str.WriteRune(opt) + } + + if check_val { + return "", fmt.Errorf(fmt.Sprintf("Haukufunga '{', tuliokota kabla ya kufika mwisho `%s'", val.String())) + } + + for _, v := range optionsMap { + if !v.val { + return "", fmt.Errorf(fmt.Sprintf("Ulipeana hili chaguo (%s) {%s} lakini haukutumia", v.obj.Inspect(), v.obj.Type())) + } + } + + return str.String(), nil +} diff --git a/object/time.go b/object/time.go new file mode 100644 index 0000000..2ce7409 --- /dev/null +++ b/object/time.go @@ -0,0 +1,108 @@ +package object + +import ( + "fmt" + "strconv" + "time" +) + +type Time struct { + TimeValue string +} + +func (t *Time) Type() ObjectType { return TIME_OBJ } +func (t *Time) Inspect() string { return t.TimeValue } +func (t *Time) Method(method string, args []Object, defs map[string]Object) Object { + switch method { + case "ongeza": + return t.add(args, defs) + case "tangu": + return t.since(args, defs) + } + return nil +} + +func (t *Time) add(args []Object, defs map[string]Object) Object { + if len(defs) != 0 { + var sec, min, hr, d, m, y int + for k, v := range defs { + objvalue := v.Inspect() + inttime, err := strconv.Atoi(objvalue) + if err != nil { + return newError("namba tu zinaruhusiwa kwenye hoja") + } + switch k { + case "sekunde": + sec = inttime + case "dakika": + min = inttime + case "saa": + hr = inttime + case "siku": + d = inttime + case "miezi": + m = inttime + case "miaka": + y = inttime + default: + return newError("Hukuweka muda sahihi") + } + } + cur_time, _ := time.Parse("15:04:05 02-01-2006", t.Inspect()) + next_time := cur_time. + Add(time.Duration(sec)*time.Second). + Add(time.Duration(min)*time.Minute). + Add(time.Duration(hr)*time.Hour). + AddDate(y, m, d) + return &Time{TimeValue: string(next_time.Format("15:04:05 02-01-2006"))} + } + + if len(args) != 1 { + return newError("Samahani, tunahitaji Hoja 1, wewe umeweka %d", len(args)) + } + + cur_time, _ := time.Parse("15:04:05 02-01-2006", t.Inspect()) + + objvalue := args[0].Inspect() + inttime, err := strconv.Atoi(objvalue) + + if err != nil { + return newError("namba tu zinaruhusiwa kwenye hoja") + } + + next_time := cur_time.Add(time.Duration(inttime) * time.Hour) + return &Time{TimeValue: string(next_time.Format("15:04:05 02-01-2006"))} +} + +func (t *Time) since(args []Object, defs map[string]Object) Object { + if len(defs) != 0 { + return &Error{Message: "Hoja hii hairuhusiwi"} + } + if len(args) != 1 { + return &Error{Message: "tunahitaji hoja moja tu"} + } + + var ( + o time.Time + err error + ) + + switch m := args[0].(type) { + case *Time: + o, _ = time.Parse("15:04:05 02-01-2006", m.TimeValue) + case *String: + o, err = time.Parse("15:04:05 02-01-2006", m.Value) + if err != nil { + return &Error{Message: fmt.Sprintf("Hoja %s sii sahihi", args[0].Inspect())} + } + default: + return &Error{Message: fmt.Sprintf("Hoja %s sii sahihi", args[0].Inspect())} + } + + ct, _ := time.Parse("15:04:05 02-01-2006", t.TimeValue) + + diff := ct.Sub(o) + durationInSeconds := diff.Seconds() + + return &Integer{Value: int64(durationInSeconds)} +} diff --git a/parser/arrays.go b/parser/arrays.go new file mode 100644 index 0000000..dca541d --- /dev/null +++ b/parser/arrays.go @@ -0,0 +1,37 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseArrayLiteral() ast.Expression { + array := &ast.ArrayLiteral{Token: p.curToken} + + array.Elements = p.parseExpressionList(token.RBRACKET) + + return array +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + return list +} diff --git a/parser/assignEqual.go b/parser/assignEqual.go new file mode 100644 index 0000000..82d2346 --- /dev/null +++ b/parser/assignEqual.go @@ -0,0 +1,38 @@ +package parser + +import ( + "fmt" + + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseAssignEqualExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier: + e := &ast.AssignEqual{ + Token: p.curToken, + Left: exp.(*ast.Identifier), + } + precendence := p.curPrecedence() + p.nextToken() + e.Value = p.parseExpression(precendence) + return e + case *ast.IndexExpression: + ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + ae.Value = p.parseExpression(LOWEST) + + return ae + default: + if node != nil { + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) + p.errors = append(p.errors, msg) + } else { + msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) + p.errors = append(p.errors, msg) + } + return nil + } +} diff --git a/parser/assignment.go b/parser/assignment.go new file mode 100644 index 0000000..2a4216c --- /dev/null +++ b/parser/assignment.go @@ -0,0 +1,49 @@ +package parser + +import ( + "fmt" + + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { + switch node := exp.(type) { + case *ast.Identifier: + e := &ast.Assign{ + Token: p.curToken, + Name: exp.(*ast.Identifier), + } + precedence := p.curPrecedence() + p.nextToken() + e.Value = p.parseExpression(precedence) + return e + + case *ast.IndexExpression: + case *ast.PropertyExpression: + e := &ast.PropertyAssignment{ + Token: p.curToken, + Name: exp.(*ast.PropertyExpression), + } + precedence := p.curPrecedence() + p.nextToken() + e.Value = p.parseExpression(precedence) + return e + default: + if node != nil { + msg := fmt.Sprintf("Mstari %d:Tulitegemea kupata kitambulishi au array, badala yake tumepata: %s", p.curToken.Line, node.TokenLiteral()) + p.errors = append(p.errors, msg) + } else { + msg := fmt.Sprintf("Mstari %d: Umekosea mkuu", p.curToken.Line) + p.errors = append(p.errors, msg) + } + return nil + } + + ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} + + p.nextToken() + + ae.Value = p.parseExpression(LOWEST) + + return ae +} diff --git a/parser/at.go b/parser/at.go new file mode 100644 index 0000000..7d0c6ad --- /dev/null +++ b/parser/at.go @@ -0,0 +1,7 @@ +package parser + +import "github.com/NuruProgramming/Nuru/ast" + +func (p *Parser) parseAt() ast.Expression { + return &ast.At{Token: p.curToken} +} diff --git a/parser/boolean.go b/parser/boolean.go new file mode 100644 index 0000000..0d3c125 --- /dev/null +++ b/parser/boolean.go @@ -0,0 +1,10 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseBoolean() ast.Expression { + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +} diff --git a/parser/break.go b/parser/break.go new file mode 100644 index 0000000..0bdc4a7 --- /dev/null +++ b/parser/break.go @@ -0,0 +1,14 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseBreak() *ast.Break { + stmt := &ast.Break{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} diff --git a/parser/continue.go b/parser/continue.go new file mode 100644 index 0000000..09b6e52 --- /dev/null +++ b/parser/continue.go @@ -0,0 +1,14 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseContinue() *ast.Continue { + stmt := &ast.Continue{Token: p.curToken} + for p.curTokenIs(token.SEMICOLON) { + p.nextToken() + } + return stmt +} diff --git a/parser/dict.go b/parser/dict.go new file mode 100644 index 0000000..450e72a --- /dev/null +++ b/parser/dict.go @@ -0,0 +1,35 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseDictLiteral() ast.Expression { + dict := &ast.DictLiteral{Token: p.curToken} + dict.Pairs = make(map[ast.Expression]ast.Expression) + + for !p.peekTokenIs(token.RBRACE) { + p.nextToken() + key := p.parseExpression(LOWEST) + + if !p.expectPeek(token.COLON) { + return nil + } + + p.nextToken() + value := p.parseExpression(LOWEST) + + dict.Pairs[key] = value + + if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { + return nil + } + } + + if !p.expectPeek(token.RBRACE) { + return nil + } + + return dict +} diff --git a/parser/dot.go b/parser/dot.go new file mode 100644 index 0000000..b46882f --- /dev/null +++ b/parser/dot.go @@ -0,0 +1,47 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseMethod(obj ast.Expression) ast.Expression { + tok := p.curToken + precedence := p.curPrecedence() + p.nextToken() + if p.peekTokenIs(token.LPAREN) { + exp := &ast.MethodExpression{Token: tok, Object: obj} + exp.Method = p.parseExpression(precedence) + if !p.expectPeek(token.LPAREN) { + return nil + } + + exp.Defaults = make(map[string]ast.Expression) + + for !p.peekTokenIs(token.RPAREN) { + p.nextToken() + if p.curTokenIs(token.COMMA) { + continue + } + if p.peekTokenIs(token.ASSIGN) { + name := p.curToken.Literal + p.nextToken() + p.nextToken() + val := p.parseExpression(LOWEST) + exp.Defaults[name] = val + } else { + exp.Arguments = append(exp.Arguments, p.parseExpression(LOWEST)) + } + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return exp + } else { + exp := &ast.PropertyExpression{Token: tok, Object: obj} + exp.Property = p.parseIdentifier() + return exp + } +} diff --git a/parser/float.go b/parser/float.go new file mode 100644 index 0000000..7544c21 --- /dev/null +++ b/parser/float.go @@ -0,0 +1,20 @@ +package parser + +import ( + "fmt" + "strconv" + + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseFloatLiteral() ast.Expression { + fl := &ast.FloatLiteral{Token: p.curToken} + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama desimali", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + fl.Value = value + return fl +} diff --git a/parser/for.go b/parser/for.go new file mode 100644 index 0000000..4548b7a --- /dev/null +++ b/parser/for.go @@ -0,0 +1,92 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseForExpression() ast.Expression { + expression := &ast.For{Token: p.curToken} + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + if !p.peekTokenIs(token.ASSIGN) { + return p.parseForInExpression(expression) + } + + // In future will allow: kwa i = 0; i<10; i++ {andika(i)} + // expression.Identifier = p.curToken.Literal + // expression.StarterName = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + // if expression.StarterName == nil { + // return nil + // } + // if !p.expectPeek(token.ASSIGN) { + // return nil + // } + + // p.nextToken() + + // expression.StarterValue = p.parseExpression(LOWEST) + // // expression.Starter = p.parseExpression(LOWEST) + // if expression.StarterValue == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Condition = p.parseExpression(LOWEST) + // if expression.Condition == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // expression.Closer = p.parseExpression(LOWEST) + // if expression.Closer == nil { + // return nil + // } + // p.nextToken() + // for p.curTokenIs(token.SEMICOLON) { + // p.nextToken() + // } + // if !p.curTokenIs(token.LBRACE) { + // return nil + // } + // expression.Block = p.parseBlockStatement() + // return expression + return nil +} + +func (p *Parser) parseForInExpression(initialExpression *ast.For) ast.Expression { + expression := &ast.ForIn{Token: initialExpression.Token} + if !p.curTokenIs(token.IDENT) { + return nil + } + val := p.curToken.Literal + var key string + p.nextToken() + if p.curTokenIs(token.COMMA) { + p.nextToken() + if !p.curTokenIs(token.IDENT) { + return nil + } + key = val + val = p.curToken.Literal + p.nextToken() + } + expression.Key = key + expression.Value = val + if !p.curTokenIs(token.IN) { + return nil + } + p.nextToken() + expression.Iterable = p.parseExpression(LOWEST) + if !p.expectPeek(token.LBRACE) { + return nil + } + expression.Block = p.parseBlockStatement() + return expression +} diff --git a/parser/function.go b/parser/function.go new file mode 100644 index 0000000..d1f710a --- /dev/null +++ b/parser/function.go @@ -0,0 +1,67 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if p.peekTokenIs(token.IDENT) { + p.nextToken() + lit.Name = p.curToken.Literal + } + + if !p.expectPeek(token.LPAREN) { + return nil + } + + if !p.parseFunctionParameters(lit) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters(lit *ast.FunctionLiteral) bool { + lit.Defaults = make(map[string]ast.Expression) + for !p.peekTokenIs(token.RPAREN) { + p.nextToken() + + if p.curTokenIs(token.COMMA) { + continue + } + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + lit.Parameters = append(lit.Parameters, ident) + + if p.peekTokenIs(token.ASSIGN) { + p.nextToken() + p.nextToken() + lit.Defaults[ident.Value] = p.parseExpression(LOWEST) + } else { + if len(lit.Defaults) > 0 { + return false + } + } + + if !(p.peekTokenIs(token.COMMA) || p.peekTokenIs(token.RPAREN)) { + return false + } + } + + return p.expectPeek(token.RPAREN) +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} diff --git a/parser/identifier.go b/parser/identifier.go new file mode 100644 index 0000000..327993b --- /dev/null +++ b/parser/identifier.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} diff --git a/parser/if.go b/parser/if.go new file mode 100644 index 0000000..fff01ed --- /dev/null +++ b/parser/if.go @@ -0,0 +1,50 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseIfExpression() ast.Expression { + expression := &ast.IfExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + if p.peekTokenIs(token.ELSE) { + p.nextToken() + if p.peekTokenIs(token.IF) { + p.nextToken() + expression.Alternative = &ast.BlockStatement{ + Statements: []ast.Statement{ + &ast.ExpressionStatement{ + Expression: p.parseIfExpression(), + }, + }, + } + return expression + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Alternative = p.parseBlockStatement() + } + + return expression +} diff --git a/parser/import.go b/parser/import.go new file mode 100644 index 0000000..c2f75f7 --- /dev/null +++ b/parser/import.go @@ -0,0 +1,24 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseImport() ast.Expression { + exp := &ast.Import{Token: p.curToken} + exp.Identifiers = make(map[string]*ast.Identifier) + for p.curToken.Line == p.peekToken.Line { + p.nextToken() + identifier := &ast.Identifier{Value: p.curToken.Literal} + exp.Identifiers[p.curToken.Literal] = identifier + if p.peekTokenIs(token.COMMA) { + p.nextToken() + } + if p.peekTokenIs(token.EOF) { + break + } + } + + return exp +} diff --git a/parser/index.go b/parser/index.go new file mode 100644 index 0000000..6e3cab7 --- /dev/null +++ b/parser/index.go @@ -0,0 +1,18 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { + exp := &ast.IndexExpression{Token: p.curToken, Left: left} + + p.nextToken() + exp.Index = p.parseExpression(LOWEST) + if !p.expectPeek(token.RBRACKET) { + return nil + } + + return exp +} diff --git a/parser/integer.go b/parser/integer.go new file mode 100644 index 0000000..46d648a --- /dev/null +++ b/parser/integer.go @@ -0,0 +1,22 @@ +package parser + +import ( + "fmt" + "strconv" + + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseIntegerLiteral() ast.Expression { + lit := &ast.IntegerLiteral{Token: p.curToken} + + value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) + if err != nil { + msg := fmt.Sprintf("Mstari %d: Hatuwezi kuparse %q kama namba", p.curToken.Line, p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + lit.Value = value + + return lit +} diff --git a/parser/null.go b/parser/null.go new file mode 100644 index 0000000..1cf11b5 --- /dev/null +++ b/parser/null.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseNull() ast.Expression { + return &ast.Null{Token: p.curToken} +} diff --git a/parser/package.go b/parser/package.go new file mode 100644 index 0000000..7e5a6c6 --- /dev/null +++ b/parser/package.go @@ -0,0 +1,18 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parsePackage() ast.Expression { + expression := &ast.Package{Token: p.curToken} + p.nextToken() + expression.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.LBRACE) { + return nil + } + expression.Block = p.parseBlockStatement() + return expression +} diff --git a/parser/parser.go b/parser/parser.go index 1018ace..0bcf5f8 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2,44 +2,61 @@ package parser import ( "fmt" - "strconv" - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/token" + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/lexer" + "github.com/NuruProgramming/Nuru/token" ) const ( - // Think of BODMAS _ int = iota LOWEST ASSIGN // = + COND // OR or AND EQUALS // == LESSGREATER // > OR < SUM // + PRODUCT // * + POWER // ** we got the power XD + MODULUS // % PREFIX // -X OR !X CALL // myFunction(X) INDEX // Arrays + DOT // For methods ) var precedences = map[token.TokenType]int{ - token.ASSIGN: ASSIGN, // Lowest priority - token.EQ: EQUALS, - token.NOT_EQ: EQUALS, - token.LT: LESSGREATER, - token.GT: LESSGREATER, - token.PLUS: SUM, - token.MINUS: SUM, - token.SLASH: PRODUCT, - token.ASTERISK: PRODUCT, + token.AND: COND, + token.OR: COND, + token.IN: COND, + token.ASSIGN: ASSIGN, + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.LTE: LESSGREATER, + token.GT: LESSGREATER, + token.GTE: LESSGREATER, + token.PLUS: SUM, + token.PLUS_ASSIGN: SUM, + token.MINUS: SUM, + token.MINUS_ASSIGN: SUM, + token.SLASH: PRODUCT, + token.SLASH_ASSIGN: PRODUCT, + token.ASTERISK: PRODUCT, + token.ASTERISK_ASSIGN: PRODUCT, + token.POW: POWER, + token.MODULUS: MODULUS, + token.MODULUS_ASSIGN: MODULUS, + // token.BANG: PREFIX, token.LPAREN: CALL, - token.LBRACKET: INDEX, // Highest priority + token.LBRACKET: INDEX, + token.DOT: DOT, // Highest priority } type ( - prefixParseFn func() ast.Expression - infixParseFn func(ast.Expression) ast.Expression + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression + postfixParseFn func() ast.Expression ) type Parser struct { @@ -47,17 +64,30 @@ type Parser struct { curToken token.Token peekToken token.Token + prevToken token.Token errors []string - prefixParseFns map[token.TokenType]prefixParseFn - infixParseFns map[token.TokenType]infixParseFn + prefixParseFns map[token.TokenType]prefixParseFn + infixParseFns map[token.TokenType]infixParseFn + postfixParseFns map[token.TokenType]postfixParseFn +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} + +func (p *Parser) registerPostfix(tokenType token.TokenType, fn postfixParseFn) { + p.postfixParseFns[tokenType] = fn } func New(l *lexer.Lexer) *Parser { p := &Parser{l: l, errors: []string{}} - // Gotta set these niggas p.nextToken() p.nextToken() @@ -65,8 +95,10 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.STRING, p.parseStringLiteral) p.registerPrefix(token.IDENT, p.parseIdentifier) p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.FLOAT, p.parseFloatLiteral) p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) + p.registerPrefix(token.PLUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) p.registerPrefix(token.FALSE, p.parseBoolean) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) @@ -75,26 +107,44 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.LBRACKET, p.parseArrayLiteral) p.registerPrefix(token.LBRACE, p.parseDictLiteral) p.registerPrefix(token.WHILE, p.parseWhileExpression) + p.registerPrefix(token.NULL, p.parseNull) + p.registerPrefix(token.FOR, p.parseForExpression) + p.registerPrefix(token.SWITCH, p.parseSwitchStatement) + p.registerPrefix(token.IMPORT, p.parseImport) + p.registerPrefix(token.PACKAGE, p.parsePackage) + p.registerPrefix(token.AT, p.parseAt) p.infixParseFns = make(map[token.TokenType]infixParseFn) + p.registerInfix(token.AND, p.parseInfixExpression) + p.registerInfix(token.OR, p.parseInfixExpression) p.registerInfix(token.PLUS, p.parseInfixExpression) + p.registerInfix(token.PLUS_ASSIGN, p.parseAssignEqualExpression) p.registerInfix(token.MINUS, p.parseInfixExpression) + p.registerInfix(token.MINUS_ASSIGN, p.parseAssignEqualExpression) p.registerInfix(token.SLASH, p.parseInfixExpression) + p.registerInfix(token.SLASH_ASSIGN, p.parseAssignEqualExpression) p.registerInfix(token.ASTERISK, p.parseInfixExpression) + p.registerInfix(token.ASTERISK_ASSIGN, p.parseAssignEqualExpression) + p.registerInfix(token.POW, p.parseInfixExpression) + p.registerInfix(token.MODULUS, p.parseInfixExpression) + p.registerInfix(token.MODULUS_ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.EQ, p.parseInfixExpression) p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) + p.registerInfix(token.LTE, p.parseInfixExpression) p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.GTE, p.parseInfixExpression) p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) - return p -} + p.registerInfix(token.IN, p.parseInfixExpression) + p.registerInfix(token.DOT, p.parseMethod) -func (p *Parser) nextToken() { - // only missing shuffle to make it a music player XD - p.curToken = p.peekToken - p.peekToken = p.l.NextToken() + p.postfixParseFns = make(map[token.TokenType]postfixParseFn) + p.registerPostfix(token.PLUS_PLUS, p.parsePostfixExpression) + p.registerPostfix(token.MINUS_MINUS, p.parsePostfixExpression) + + return p } func (p *Parser) ParseProgram() *ast.Program { @@ -110,58 +160,12 @@ func (p *Parser) ParseProgram() *ast.Program { return program } -func (p *Parser) parseStatement() ast.Statement { - // Remember to add switch statements to the language - switch p.curToken.Type { - case token.LET: - return p.parseLetStatment() - case token.RETURN: - return p.parseReturnStatement() - default: - return p.parseExpressionStatement() - } -} - -func (p *Parser) parseLetStatment() *ast.LetStatement { - stmt := &ast.LetStatement{Token: p.curToken} - - if !p.expectPeek(token.IDENT) { - return nil - } - - stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - - if !p.expectPeek(token.ASSIGN) { - return nil - } - - p.nextToken() - - stmt.Value = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - -func (p *Parser) parseAssignmentExpression(exp ast.Expression) ast.Expression { - switch node := exp.(type) { - case *ast.Identifier, *ast.IndexExpression: - default: - msg := fmt.Sprintf("Tulitegemea kupata kitambulishi au array, badala yake tumepata: %T %#v", node, exp) - p.errors = append(p.errors, msg) - return nil - } - - ae := &ast.AssignmentExpression{Token: p.curToken, Left: exp} - - p.nextToken() - - ae.Value = p.parseExpression(LOWEST) +// manage token literals: - return ae +func (p *Parser) nextToken() { + p.prevToken = p.curToken + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() } func (p *Parser) curTokenIs(t token.TokenType) bool { @@ -182,36 +186,34 @@ func (p *Parser) expectPeek(t token.TokenType) bool { } } -func (p *Parser) Errors() []string { - return p.errors -} - -func (p *Parser) peekError(t token.TokenType) { - msg := fmt.Sprintf("Tulitegemea kupata %s, badala yake tumepata %s", t, p.peekToken.Type) - p.errors = append(p.errors, msg) +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + return LOWEST } -func (p *Parser) parseReturnStatement() *ast.ReturnStatement { - stmt := &ast.ReturnStatement{Token: p.curToken} - p.nextToken() - - stmt.ReturnValue = p.parseExpression(LOWEST) - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p } - return stmt + return LOWEST } -func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { - p.prefixParseFns[tokenType] = fn +// error messages + +func (p *Parser) Errors() []string { + return p.errors } -func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { - p.infixParseFns[tokenType] = fn +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tulitegemea kupata %s, badala yake tumepata %s", p.curToken.Line, t, p.peekToken.Type) + p.errors = append(p.errors, msg) } +// parse expressions + func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { stmt := &ast.ExpressionStatement{Token: p.curToken} @@ -224,12 +226,11 @@ func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { return stmt } -func (p *Parser) noPrefixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("Tumeshindwa kuparse %s", t) - p.errors = append(p.errors, msg) -} - func (p *Parser) parseExpression(precedence int) ast.Expression { + postfix := p.postfixParseFns[p.curToken.Type] + if postfix != nil { + return (postfix()) + } prefix := p.prefixParseFns[p.curToken.Type] if prefix == nil { p.noPrefixParseFnError(p.curToken.Type) @@ -240,7 +241,8 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { infix := p.infixParseFns[p.peekToken.Type] if infix == nil { - return leftExp + p.noInfixParseFnError(p.peekToken.Type) + return nil } p.nextToken() @@ -250,23 +252,7 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { } -func (p *Parser) parseIdentifier() ast.Expression { - return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseIntegerLiteral() ast.Expression { - lit := &ast.IntegerLiteral{Token: p.curToken} - - value, err := strconv.ParseInt(p.curToken.Literal, 0, 64) - if err != nil { - msg := fmt.Sprintf("Hatuwezi kuparse %q kama namba", p.curToken.Literal) - p.errors = append(p.errors, msg) - return nil - } - lit.Value = value - - return lit -} +// prefix expressions func (p *Parser) parsePrefixExpression() ast.Expression { expression := &ast.PrefixExpression{ @@ -281,20 +267,12 @@ func (p *Parser) parsePrefixExpression() ast.Expression { return expression } -func (p *Parser) peekPrecedence() int { - if p, ok := precedences[p.peekToken.Type]; ok { - return p - } - return LOWEST +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) } -func (p *Parser) curPrecedence() int { - if p, ok := precedences[p.curToken.Type]; ok { - return p - } - - return LOWEST -} +// infix expressions func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { expression := &ast.InfixExpression{ @@ -309,8 +287,9 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { return expression } -func (p *Parser) parseBoolean() ast.Expression { - return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} +func (p *Parser) noInfixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("Mstari %d: Tumeshindwa kuparse %s", p.curToken.Line, t) + p.errors = append(p.errors, msg) } func (p *Parser) parseGroupedExpression() ast.Expression { @@ -325,211 +304,12 @@ func (p *Parser) parseGroupedExpression() ast.Expression { return exp } -func (p *Parser) parseIfExpression() ast.Expression { - expression := &ast.IfExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Consequence = p.parseBlockStatement() - - if p.peekTokenIs(token.ELSE) { - p.nextToken() - if p.peekTokenIs(token.IF) { - p.nextToken() - expression.Alternative = &ast.BlockStatement{ - Statements: []ast.Statement{ - &ast.ExpressionStatement{ - Expression: p.parseIfExpression(), - }, - }, - } - return expression - } - - if !p.expectPeek(token.LBRACE) { - return nil - } - - expression.Alternative = p.parseBlockStatement() - } - - return expression -} - -func (p *Parser) parseBlockStatement() *ast.BlockStatement { - block := &ast.BlockStatement{Token: p.curToken} - block.Statements = []ast.Statement{} - - p.nextToken() - - for !p.curTokenIs(token.RBRACE) && !p.curTokenIs(token.EOF) { - stmt := p.parseStatement() - block.Statements = append(block.Statements, stmt) - p.nextToken() - } - - return block -} - -func (p *Parser) parseFunctionLiteral() ast.Expression { - lit := &ast.FunctionLiteral{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - lit.Parameters = p.parseFunctionParameters() - - if !p.expectPeek(token.LBRACE) { - return nil - } - - lit.Body = p.parseBlockStatement() - - return lit -} - -func (p *Parser) parseFunctionParameters() []*ast.Identifier { - identifiers := []*ast.Identifier{} - - if p.peekTokenIs(token.RPAREN) { - p.nextToken() - return identifiers - } - - p.nextToken() - - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - identifiers = append(identifiers, ident) - } - - if !p.expectPeek(token.RPAREN) { - return nil - } - - return identifiers -} - -func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { - exp := &ast.CallExpression{Token: p.curToken, Function: function} - exp.Arguments = p.parseExpressionList(token.RPAREN) - return exp -} - -func (p *Parser) parseStringLiteral() ast.Expression { - return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} -} - -func (p *Parser) parseArrayLiteral() ast.Expression { - array := &ast.ArrayLiteral{Token: p.curToken} +// postfix expressions - array.Elements = p.parseExpressionList(token.RBRACKET) - - return array -} - -func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { - list := []ast.Expression{} - - if p.peekTokenIs(end) { - p.nextToken() - return list - } - - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - - for p.peekTokenIs(token.COMMA) { - p.nextToken() - p.nextToken() - list = append(list, p.parseExpression(LOWEST)) - } - - if !p.expectPeek(end) { - return nil - } - return list -} - -func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression { - exp := &ast.IndexExpression{Token: p.curToken, Left: left} - - p.nextToken() - exp.Index = p.parseExpression(LOWEST) - if !p.expectPeek(token.RBRACKET) { - return nil - } - - return exp -} - -func (p *Parser) parseDictLiteral() ast.Expression { - dict := &ast.DictLiteral{Token: p.curToken} - dict.Pairs = make(map[ast.Expression]ast.Expression) - - for !p.peekTokenIs(token.RBRACE) { - p.nextToken() - key := p.parseExpression(LOWEST) - - if !p.expectPeek(token.COLON) { - return nil - } - - p.nextToken() - value := p.parseExpression(LOWEST) - - dict.Pairs[key] = value - - if !p.peekTokenIs(token.RBRACE) && !p.expectPeek(token.COMMA) { - return nil - } - } - - if !p.expectPeek(token.RBRACE) { - return nil - } - - return dict -} - -func (p *Parser) parseWhileExpression() ast.Expression { - expression := &ast.WhileExpression{Token: p.curToken} - - if !p.expectPeek(token.LPAREN) { - return nil - } - - p.nextToken() - expression.Condition = p.parseExpression(LOWEST) - - if !p.expectPeek(token.RPAREN) { - return nil - } - - if !p.expectPeek(token.LBRACE) { - return nil +func (p *Parser) parsePostfixExpression() ast.Expression { + expression := &ast.PostfixExpression{ + Token: p.prevToken, + Operator: p.curToken.Literal, } - - expression.Consequence = p.parseBlockStatement() - return expression } diff --git a/parser/parser_test.go b/parser/parser_test.go index 19bd04e..50e880e 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/AvicennaJr/Nuru/ast" - "github.com/AvicennaJr/Nuru/lexer" + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/lexer" ) func TestLetStatements(t *testing.T) { @@ -14,9 +14,9 @@ func TestLetStatements(t *testing.T) { expectedIdentifier string expectedValue interface{} }{ - {"acha x = 5;", "x", 5}, - {"acha y = x;", "y", "x"}, - {"acha bangi = y;", "bangi", "y"}, + {"fanya x = 5;", "x", 5}, + {"fanya y = x;", "y", "x"}, + {"fanya bangi = y;", "bangi", "y"}, } for _, tt := range tests { @@ -43,8 +43,8 @@ func TestLetStatements(t *testing.T) { } func testLetStatement(t *testing.T, s ast.Statement, name string) bool { - if s.TokenLiteral() != "acha" { - t.Errorf("s.TokenLiteral not 'acha', got = %q", s.TokenLiteral()) + if s.TokenLiteral() != "fanya" { + t.Errorf("s.TokenLiteral not 'fanya', got = %q", s.TokenLiteral()) return false } @@ -257,6 +257,10 @@ func TestParsingInfixExpressions(t *testing.T) { {"5 < 5;", 5, "<", 5}, {"5 == 5;", 5, "==", 5}, {"5 != 5;", 5, "!=", 5}, + {"5 >= 5;", 5, ">=", 5}, + {"5 <= 5;", 5, "<=", 5}, + {"5 || 5;", 5, "||", 5}, + {"5 && 5;", 5, "&&", 5}, {"kweli == kweli", true, "==", true}, {"kweli != sikweli", true, "!=", false}, {"sikweli == sikweli", false, "==", false}, @@ -633,7 +637,7 @@ func TestIfElseExpression(t *testing.T) { } func TestFunctionLiteralParsing(t *testing.T) { - input := `fn(x, y) {x + y}` + input := `unda(x, y) {x + y}` l := lexer.New(input) p := New(l) @@ -678,9 +682,9 @@ func TestFunctionParameterParsing(t *testing.T) { input string expectedParams []string }{ - {input: "fn() {};", expectedParams: []string{}}, - {input: "fn(x) {};", expectedParams: []string{"x"}}, - {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + {input: "unda() {};", expectedParams: []string{}}, + {input: "unda(x) {};", expectedParams: []string{"x"}}, + {input: "unda(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, } for _, tt := range tests { @@ -968,3 +972,155 @@ func TestParsingEmptyDict(t *testing.T) { t.Errorf("Dict pairs has wrong length, got=%d", len(dict.Pairs)) } } + +func TestWhileLoop(t *testing.T) { + input := `wakati ( x > y ) { fanya x = 2 }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression, got=%T", program.Statements[0]) + } + exp, ok := stmt.Expression.(*ast.WhileExpression) + + if !ok { + t.Fatalf("stmt.Expression is not ast.WhileExpression. got=%T", stmt.Expression) + } + + if !testInfixExpression(t, exp.Condition, "x", ">", "y") { + return + } + + if len(exp.Consequence.Statements) != 1 { + t.Errorf("Consequence is not 1 statements. got=%d\n", len(exp.Consequence.Statements)) + } + + consequence, ok := exp.Consequence.Statements[0].(*ast.LetStatement) + + if !ok { + t.Fatalf("exp.Consequence.Statements[0] is not ast.ExpressionStatement. got=%T", exp.Consequence.Statements[0]) + } + + if !testLetStatement(t, consequence, "x") { + t.Fatalf("exp.Consequence is not LetStatement") + } +} + +func TestShorthandAssignment(t *testing.T) { + input := []string{ + "fanya x = 10; x *= 20;", + "fanya x = 5; x += 4;", + "fanya x = 7; x /= 2;", + "fanya x = 8; x -= 1;", + "fanya x = 5; x++;", + "fanya x = 3; x--;", + "fanya x = 40; fanya y = 13; x += y;"} + + for _, txt := range input { + l := lexer.New(txt) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) + } +} + +func TestForExpression(t *testing.T) { + input := `kwa i, v ktk j {andika(i)}` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Body does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.Expression, got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.ForIn) + + if !ok { + t.Fatalf("stmt.Expression is not ast.ForIn. got=%T", stmt.Expression) + } + + if exp.Key != "i" { + t.Fatalf("Wrong Key Index, expected 'i' got %s", exp.Key) + } + + if exp.Value != "v" { + t.Fatalf("Wrong Value Index, expected 'v' got %s", exp.Value) + } +} + +func TestParsePostfix(t *testing.T) { + input := []string{ + "a = 5; a++;", + "b = 5; b--;", + } + + for _, txt := range input { + l := lexer.New(txt) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) + } +} + +func TestParseDot(t *testing.T) { + input := []string{ + "a.b()", + "5.5", + } + + for _, txt := range input { + l := lexer.New(txt) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) + } +} + +func TestParseSwitch(t *testing.T) { + input := ` + badili (a) { + ikiwa 2 { + andika(2) + } + ikiwa 3 { + andika(3) + } + kawaida { + andika(0) + } + } + ` + + l := lexer.New(input) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) +} + +func TestParseImport(t *testing.T) { + input := ` + tumia muda + muda.hasahivi() + ` + + l := lexer.New(input) + p := New(l) + _ = p.ParseProgram() + checkParserErrors(t, p) +} diff --git a/parser/statements.go b/parser/statements.go new file mode 100644 index 0000000..7e07f23 --- /dev/null +++ b/parser/statements.go @@ -0,0 +1,80 @@ +package parser + +import ( + "fmt" + + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseStatement() ast.Statement { + switch p.curToken.Type { + case token.LET: + return p.parseLetStatement() + case token.RETURN: + return p.parseReturnStatement() + case token.BREAK: + return p.parseBreak() + case token.CONTINUE: + return p.parseContinue() + default: + return p.parseExpressionStatement() + } +} + +func (p *Parser) parseLetStatement() *ast.LetStatement { + stmt := &ast.LetStatement{Token: p.curToken} + + if !p.expectPeek(token.IDENT) { + return nil + } + + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + + if !p.expectPeek(token.ASSIGN) { + return nil + } + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseReturnStatement() *ast.ReturnStatement { + stmt := &ast.ReturnStatement{Token: p.curToken} + p.nextToken() + + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { + p.nextToken() + } + + return stmt +} + +func (p *Parser) parseBlockStatement() *ast.BlockStatement { + block := &ast.BlockStatement{Token: p.curToken} + block.Statements = []ast.Statement{} + + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Hukufunga Mabano '}'", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + stmt := p.parseStatement() + block.Statements = append(block.Statements, stmt) + p.nextToken() + } + + return block +} diff --git a/parser/string.go b/parser/string.go new file mode 100644 index 0000000..8a023fa --- /dev/null +++ b/parser/string.go @@ -0,0 +1,9 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" +) + +func (p *Parser) parseStringLiteral() ast.Expression { + return &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} +} diff --git a/parser/switch.go b/parser/switch.go new file mode 100644 index 0000000..ebffc63 --- /dev/null +++ b/parser/switch.go @@ -0,0 +1,89 @@ +package parser + +import ( + "fmt" + + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseSwitchStatement() ast.Expression { + expression := &ast.SwitchExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Value = p.parseExpression(LOWEST) + + if expression.Value == nil { + return nil + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + p.nextToken() + + for !p.curTokenIs(token.RBRACE) { + + if p.curTokenIs(token.EOF) { + msg := fmt.Sprintf("Mstari %d: Haukufunga ENDAPO (SWITCH)", p.curToken.Line) + p.errors = append(p.errors, msg) + return nil + } + tmp := &ast.CaseExpression{Token: p.curToken} + + if p.curTokenIs(token.DEFAULT) { + + tmp.Default = true + + } else if p.curTokenIs(token.CASE) { + + p.nextToken() + + if p.curTokenIs(token.DEFAULT) { + tmp.Default = true + } else { + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + tmp.Expr = append(tmp.Expr, p.parseExpression(LOWEST)) + } + } + } else { + msg := fmt.Sprintf("Mstari %d: Tulitegemea Kauli IKIWA (CASE) au KAWAIDA (DEFAULT) lakini tumepewa: %s", p.curToken.Line, p.curToken.Type) + p.errors = append(p.errors, msg) + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + tmp.Block = p.parseBlockStatement() + p.nextToken() + expression.Choices = append(expression.Choices, tmp) + } + + count := 0 + for _, c := range expression.Choices { + if c.Default { + count++ + } + } + if count > 1 { + msg := fmt.Sprintf("Kauli ENDAPO (SWITCH) hua na kauli 'KAWAIDA' (DEFAULT) moja tu! Wewe umeweka %d", count) + p.errors = append(p.errors, msg) + return nil + + } + return expression + +} diff --git a/parser/while.go b/parser/while.go new file mode 100644 index 0000000..1d7a202 --- /dev/null +++ b/parser/while.go @@ -0,0 +1,29 @@ +package parser + +import ( + "github.com/NuruProgramming/Nuru/ast" + "github.com/NuruProgramming/Nuru/token" +) + +func (p *Parser) parseWhileExpression() ast.Expression { + expression := &ast.WhileExpression{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + p.nextToken() + expression.Condition = p.parseExpression(LOWEST) + + if !p.expectPeek(token.RPAREN) { + return nil + } + + if !p.expectPeek(token.LBRACE) { + return nil + } + + expression.Consequence = p.parseBlockStatement() + + return expression +} diff --git a/repl/docs.go b/repl/docs.go new file mode 100644 index 0000000..b8fd428 --- /dev/null +++ b/repl/docs.go @@ -0,0 +1,334 @@ +package repl + +import ( + "fmt" + "strings" + + "github.com/NuruProgramming/Nuru/evaluator" + "github.com/NuruProgramming/Nuru/lexer" + "github.com/NuruProgramming/Nuru/object" + "github.com/NuruProgramming/Nuru/parser" + "github.com/NuruProgramming/Nuru/styles" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" +) + +var ( + buttonStyle = lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder(), true, false). + Padding(0, 3). + MarginTop(1) + + activeButtonStyle = buttonStyle.Copy(). + Foreground(lipgloss.Color("#FFF7DB")). + Background(lipgloss.Color("#aa6f5a")). + Margin(0, 2). + Underline(true) + + tableOfContentStyle = lipgloss.NewStyle().Margin(1, 2).BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("#aa6f5a")). + Foreground(lipgloss.Color("#aa6f5a")). + Padding(2) +) + +type item struct { + title, desc, filename string +} + +func (i item) Title() string { return i.title } +func (i item) Description() string { return i.desc } +func (i item) FilterValue() string { return i.title } + +type languages struct { + title, desc, dir string +} + +func (l languages) Title() string { return l.title } +func (l languages) Description() string { return l.desc } +func (l languages) FilterValue() string { return l.title } + +type playground struct { + id string + output viewport.Model + code string + editor textarea.Model + docs viewport.Model + ready bool + filename string + content []byte + mybutton string + fileSelected bool + toc list.Model + windowWidth int + windowHeight int + docRenderer *glamour.TermRenderer + language string + languageCursor list.Model +} + +func (pg playground) Init() tea.Cmd { + return textarea.Blink +} + +func (pg playground) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + edCmd tea.Cmd + opCmd tea.Cmd + docCmd tea.Cmd + tocCmd tea.Cmd + ) + + pg.editor, edCmd = pg.editor.Update(msg) + pg.output, opCmd = pg.output.Update(msg) + pg.languageCursor, _ = pg.languageCursor.Update(msg) + if !pg.fileSelected { + pg.toc, tocCmd = pg.toc.Update(msg) + } + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyCtrlC: + fmt.Println(pg.editor.Value()) + return pg, tea.Quit + case tea.KeyEnter: + if pg.language == "" { + i, ok := pg.languageCursor.SelectedItem().(languages) + if ok { + pg.language = i.dir + if pg.language == "en" { + pg.toc = list.New(englishItems, list.NewDefaultDelegate(), pg.windowWidth/2-4, pg.windowHeight-8) + pg.toc.Title = "Table of Contents" + } else { + pg.toc = list.New(kiswahiliItems, list.NewDefaultDelegate(), pg.windowWidth/2-4, pg.windowHeight-8) + pg.toc.Title = "Yaliyomo" + } + return pg, tea.EnterAltScreen + } + } + i, ok := pg.toc.SelectedItem().(item) + if ok { + pg.filename = i.filename + content, err := res.ReadFile("docs/" + pg.language + "/" + pg.filename) + if err != nil { + panic(err) + } + pg.content = content + str, err := pg.docRenderer.Render(string(pg.content)) + if err != nil { + panic(err) + } + + pg.docs.SetContent(str + "\n\n\n\n\n\n") + + if err != nil { + panic(err) + } + pg.fileSelected = true + pg.editor.Focus() + } + case tea.KeyCtrlR: + if strings.Contains(pg.editor.Value(), "jaza") { + pg.output.SetContent(styles.HelpStyle.Italic(false).Render("Samahani, huwezi kutumia `jaza()` kwa sasa.")) + } else { + // this is just for the output will find a better solution + code := strings.ReplaceAll(pg.editor.Value(), "andika", "_andika") + pg.code = code + env := object.NewEnvironment() + l := lexer.New(pg.code) + p := parser.New(l) + program := p.ParseProgram() + if len(p.Errors()) != 0 { + pg.output.Style = styles.ErrorStyle.PaddingLeft(3) + pg.output.SetContent(strings.Join(p.Errors(), "\n")) + } else { + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + if evaluated.Type() != object.NULL_OBJ { + pg.output.Style = styles.ReplStyle.PaddingLeft(3) + content := evaluated.Inspect() + l := strings.Split(content, "\n") + if len(l) > 15 { + content = strings.Join(l[len(l)-16:], "\n") + } + pg.output.SetContent(content) + } + } + } + } + case tea.KeyEsc: + if pg.fileSelected { + pg.fileSelected = false + pg.editor.Blur() + } + } + + case tea.MouseMsg: + if zone.Get(pg.id + "docs").InBounds(msg) { + pg.docs, docCmd = pg.docs.Update(msg) + + } + switch msg.Type { + case tea.MouseLeft: + if zone.Get(pg.id + "run").InBounds(msg) { + if strings.Contains(pg.editor.Value(), "jaza") { + pg.output.SetContent(styles.HelpStyle.Italic(false).Render("Samahani, huwezi kutumia `jaza()` kwa sasa.")) + } else { + // this is just for the output will find a better solution + code := strings.ReplaceAll(pg.editor.Value(), "andika", "_andika") + pg.code = code + env := object.NewEnvironment() + l := lexer.New(pg.code) + p := parser.New(l) + program := p.ParseProgram() + if len(p.Errors()) != 0 { + pg.output.Style = styles.ErrorStyle.PaddingLeft(3) + pg.output.SetContent(strings.Join(p.Errors(), "\n")) + } else { + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + if evaluated.Type() != object.NULL_OBJ { + pg.output.Style = styles.ReplStyle.PaddingLeft(3) + content := evaluated.Inspect() + l := strings.Split(content, "\n") + if len(l) > 15 { + content = strings.Join(l[len(l)-16:], "\n") + } + pg.output.SetContent(content) + } + } + } + } + + } + } + case tea.WindowSizeMsg: + if !pg.ready { + // editor code + pg.editor = textarea.New() + if pg.language == "en" { + pg.editor.Placeholder = "Write Nuru code here..." + } else { + pg.editor.Placeholder = "Andika code yako hapa..." + } + + pg.editor.Prompt = "┃ " + pg.editor.SetWidth(msg.Width / 2) + pg.editor.SetHeight((2 * msg.Height / 3) - 4) + + pg.editor.CharLimit = 0 + pg.editor.FocusedStyle.CursorLine = lipgloss.NewStyle() + pg.editor.FocusedStyle.Base = lipgloss.NewStyle().PaddingTop(2). + Border(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("238")) + + pg.editor.ShowLineNumbers = true + + // output of editor + pg.output = viewport.New(msg.Width/2, msg.Height/3-4) + pg.output.Style = lipgloss.NewStyle().PaddingLeft(3) + var output string + if pg.language == "en" { + + output = "Your code output will be displayed here..." + strings.Repeat(" ", msg.Width-6) + } else { + output = "Matokeo hapa..." + strings.Repeat(" ", msg.Width-6) + } + pg.output.SetContent(output) + + // documentation + pg.docs = viewport.New(msg.Width/2, msg.Height) + pg.docs.KeyMap = viewport.KeyMap{ + Up: key.NewBinding( + key.WithKeys("up"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + ), + } + pg.docs.Style = lipgloss.NewStyle(). + BorderStyle(lipgloss.RoundedBorder()). + BorderForeground(lipgloss.Color("62")). + Padding(2) + + renderer, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(msg.Width/2-4), + ) + if err != nil { + panic(err) + } + + pg.docRenderer = renderer + + pg.toc.SetSize(msg.Width, msg.Height-8) + pg.windowWidth = msg.Width + pg.windowHeight = msg.Height + + if pg.language == "en" { + pg.mybutton = activeButtonStyle.Width(msg.Width / 2).Height(1).Align(lipgloss.Center).Render("Run (CTRL + R)") + } else { + + pg.mybutton = activeButtonStyle.Width(msg.Width / 2).Height(1).Align(lipgloss.Center).Render("Run (CTRL + R)") + } + pg.ready = true + + } else { + pg.editor.SetHeight((2 * msg.Height / 3) - 4) + pg.editor.SetWidth(msg.Width / 2) + pg.output.Height = msg.Height/3 - 4 + pg.output.Width = msg.Width / 2 + + renderer, err := glamour.NewTermRenderer( + glamour.WithAutoStyle(), + glamour.WithWordWrap(msg.Width/2-4), + ) + if err != nil { + panic(err) + } + + pg.docRenderer = renderer + str, err := pg.docRenderer.Render(string(pg.content)) + if err != nil { + panic(err) + } + pg.docs.Height = msg.Height + pg.docs.Width = msg.Width / 2 + + pg.docs.SetContent(str + "\n\n\n\n\n\n") + if pg.language == "en" { + pg.mybutton = activeButtonStyle.Width(msg.Width / 2).Height(1).Align(lipgloss.Center).Render("Run (CTRL + R)") + } else { + pg.mybutton = activeButtonStyle.Width(msg.Width / 2).Height(1).Align(lipgloss.Center).Render("Run (CTRL + R)") + } + pg.toc.SetSize(msg.Width, msg.Height-8) + pg.windowWidth = msg.Width + pg.windowHeight = msg.Height + } + } + + return pg, tea.Batch(edCmd, opCmd, docCmd, tocCmd) +} + +func (pg playground) View() string { + if pg.language == "" { + return lipgloss.NewStyle().PaddingTop(1).Render(pg.languageCursor.View()) + } + if !pg.ready { + return "\n Tunakuandalia....." + } + var docs string + if !pg.fileSelected { + docs = zone.Mark(pg.id+"toc", tableOfContentStyle.Width(pg.windowWidth/2-4).Height(pg.windowHeight-8).Render(pg.toc.View())) + } else { + docs = zone.Mark(pg.id+"docs", pg.docs.View()) + } + button := zone.Mark(pg.id+"run", pg.mybutton) + return zone.Scan(lipgloss.JoinHorizontal(lipgloss.Center, docs, lipgloss.JoinVertical(lipgloss.Left, pg.editor.View(), button, pg.output.View()))) +} diff --git a/repl/docs/en/README.md b/repl/docs/en/README.md new file mode 100644 index 0000000..ae3e1c8 --- /dev/null +++ b/repl/docs/en/README.md @@ -0,0 +1,125 @@ +# NURU PROGRAMMING LANGUAGE DOCUMENTATION + +This documentation is intended for people with some experience in programming. It describes the syntax, types and how to perform various operations using the language. + +## Table Of Contents + +- [Arrays in Nuru](arrays.md#arrays-in-nuru) + - [Creating Arrays](arrays.md#creating-arrays) + - [Accessing and Modifying Array Elements](arrays.md#accessing-and-modifying-array-elements) + - [Concatenating Arrays](arrays.md#concatenating-arrays) + - [Checking for Array Membership](arrays.md#checking-for-array-membership) + - [Looping Over Arrays](arrays.md#looping-over-arrays) + - [Array Methods](arrays.md#array-methods) + - [idadi()](arrays.md#idadi()) + - [sukuma()](arrays.md#sukuma()) + - [yamwisho()](arrays.md#yamwisho()) +- [Built-in Functions in Nuru](builtins.md#built-in-functions-in-nuru) + - [The andika() Function](builtins.md#the-andika()-function) + - [The jaza() Function](builtins.md#the-jaza()-function) + - [The aina() Function](builtins.md#the-aina()-function) + - [The fungua() Function](builtins.md#the-fungua()-function) +- [Comments in Nuru](comments.md#comments-in-nuru) + - [Single-Line Comments](comments.md#single-line-comments) + - [Multi-Line Comments](comments.md#multi-line-comments) +- [Conditional Statements in Nuru](ifStatements.md#conditional-statements-in-nuru) + - [If Statement (Kama)](ifStatements.md#if-statement-(kama)) + - [Else If and Else Blocks (Au Kama and Sivyo)](ifStatements.md#else-if-and-else-blocks-(au-kama-and-sivyo)) +- [Dictionaries in Nuru](dictionaries.md#dictionaries-in-nuru) + - [Creating Dictionaries](dictionaries.md#creating-dictionaries) + - [Accessing Elements](dictionaries.md#accessing-elements) + - [Updating Elements](dictionaries.md#updating-elements) + - [Adding New Elements](dictionaries.md#adding-new-elements) + - [Concatenating Dictionaries](dictionaries.md#concatenating-dictionaries) + - [Checking If a Key Exists in a Dictionary](dictionaries.md#checking-if-a-key-exists-in-a-dictionary) + - [Looping Over a Dictionary](dictionaries.md#looping-over-a-dictionary) +- [Files in Nuru](files.md#files-in-nuru) + - [Opening a File](files.md#opening-a-file) + - [Reading a File](files.md#reading-a-file) +- [For Loops in Nuru](for.md#for-loops-in-nuru) + - [Basic Syntax](for.md#basic-syntax) + - [Iterating Over Key-Value Pairs](for.md#iterating-over-key-value-pairs) + - [Dictionaries](for.md#dictionaries) + - [Strings](for.md#strings) + - [Lists](for.md#lists) + - [Break (Vunja) and Continue (Endelea)](for.md#break-(vunja)-and-continue-(endelea)) + - [Break (Vunja)](for.md#break-(vunja)) + - [Continue (Endelea)](for.md#continue-(endelea)) +- [Functions in Nuru](function.md#functions-in-nuru) + - [Basic Syntax](function.md#basic-syntax) + - [Parameters](function.md#parameters) + - [Default Parameters](function.md#default-parameters) + - [Return (rudisha)](function.md#return-(rudisha)) + - [Recursion](function.md#recursion) + - [Closures](function.md#closures) +- [HTTP with Nuru](net.md#http-with-nuru) + - [Importing](net.md#importing) + - [Methods](net.md#methods) + - [peruzi()](net.md#peruzi()) + - [tuma()](net.md#tuma()) +- [Identifiers in Nuru](identifiers.md#identifiers-in-nuru) + - [Syntax Rules](identifiers.md#syntax-rules) + - [Best Practices](identifiers.md#best-practices) +- [INTEGERS (NAMBA) AND FLOATS (DESIMALI)](numbers.md#integers-(namba)-and-floats-(desimali)) + - [PRECEDENCE](numbers.md#precedence) + - [UNARY INCREMENTS](numbers.md#unary-increments) + - [SHORTHAND ASSIGNMENT](numbers.md#shorthand-assignment) + - [NEGATIVE NUMBERS](numbers.md#negative-numbers) +- [JSON in Nuru](json.md#json-in-nuru) + - [Import JSONI](json.md#import-jsoni) + - [Decoding JSON with dikodi()](json.md#decoding-json-with-dikodi()) + - [Encoding JSON with enkodi()](json.md#encoding-json-with-enkodi()) +- [Hisabati in nuru](hisabati.md#module-hisabati) + - [Import Hisabati](hisabati.md#usage) + - [In-built Constants](hisabati.md#1-constants) + - [In-built Methods](hisabati.md#2-methods) +- [KEYWORDS](keywords.md#keywords) + - [Reserved Keywords](keywords.md#reserved-keywords) + - [BuiltIns](keywords.md#builtins) +- [Null (Tupu) in Nuru](null.md#null-(tupu)-in-nuru) + - [Definition](null.md#definition) + - [Evaluation](null.md#evaluation) +- [NURU PROGRAMMING LANGUAGE DOCUMENTATION](README.md#nuru-programming-language-documentation) + - [Table Of Contents](README.md#table-of-contents) +- [OPERATORS](operators.md#operators) + - [ASSIGNMENT](operators.md#assignment) + - [ARITHMETIC OPERATORS](operators.md#arithmetic-operators) + - [COMPARISON OPERATORS](operators.md#comparison-operators) + - [MEMBER OPERATOR](operators.md#member-operator) + - [LOGIC OPERATORS](operators.md#logic-operators) + - [PRECEDENCE OF OPERATORS](operators.md#precedence-of-operators) +- [Strings in Nuru](strings.md#strings-in-nuru) + - [Basic Syntax](strings.md#basic-syntax) + - [Concatenating Strings](strings.md#concatenating-strings) + - [Looping over a String](strings.md#looping-over-a-string) + - [Comparing Strings](strings.md#comparing-strings) + - [String Methods](strings.md#string-methods) + - [idadi()](strings.md#idadi()) + - [herufikubwa()](strings.md#herufikubwa()) + - [herufindogo](strings.md#herufindogo) + - [gawa](strings.md#gawa) +- [Switch Statements in Nuru](switch.md#switch-statements-in-nuru) + - [Basic Syntax](switch.md#basic-syntax) + - [Multiple Values in a Case](switch.md#multiple-values-in-a-case) + - [Default Case (kawaida)](switch.md#default-case-(kawaida)) +- [Time in Nuru](time.md#time-in-nuru) + - [Importing Time](time.md#importing-time) + - [Time Methods](time.md#time-methods) + - [hasahivi()](time.md#hasahivi()) + - [tangu()](time.md#tangu()) + - [lala()](time.md#lala()) + - [ongeza()](time.md#ongeza()) +- [WHILE (WAKATI)](while.md#while-(wakati)) + - [Basic Syntax](while.md#basic-syntax) + - [Break (vunja) and Continue (endelea)](while.md#break-(vunja)-and-continue-(endelea)) + - [Break (Vunja)](while.md#break-(vunja)) + - [Continue (Endelea)](while.md#continue-(endelea)) +- [Working with Booleans in Nuru](bool.md#working-with-booleans-in-nuru) + - [Evaluating Boolean Expressions](bool.md#evaluating-boolean-expressions) + - [Evaluating Simple Expressions](bool.md#evaluating-simple-expressions) + - [Evaluating Complex Expressions](bool.md#evaluating-complex-expressions) + - [Boolean Operators](bool.md#boolean-operators) + - [The && Operator](bool.md#the-&&-operator) + - [The || Operator](bool.md#the-||-operator) + - [The ! Operator](bool.md#the-!-operator) + - [Working with Boolean Values in Loops](bool.md#working-with-boolean-values-in-loops) diff --git a/repl/docs/en/arrays.md b/repl/docs/en/arrays.md new file mode 100644 index 0000000..fffa9d9 --- /dev/null +++ b/repl/docs/en/arrays.md @@ -0,0 +1,133 @@ +# Arrays in Nuru + +Arrays in Nuru are versatile data structures that can hold multiple items, including different types such as numbers, strings, booleans, functions, and null values. This page covers various aspects of arrays, including how to create, manipulate, and iterate over them using Nuru's built-in keywords and methods. + +## Creating Arrays + +To create an array, use square brackets [] and separate items with commas: + +```s +orodha = [1, "pili", kweli] +``` +## Accessing and Modifying Array Elements + +Arrays in Nuru are zero-indexed. To access an element, use the element's index in square brackets: + +```s +namba = [10, 20, 30] +jina = namba[1] // jina is 20 +``` + +You can reassign an element in an array using its index: + +```s +namba[1] = 25 +``` + +## Concatenating Arrays + +To concatenate two or more arrays, use the + operator: + +```s +a = [1, 2, 3] +b = [4, 5, 6] +c = a + b +// c is now [1, 2, 3, 4, 5, 6] +``` + +## Checking for Array Membership + +Use the `ktk` keyword to check if an item exists in an array: + +```s +namba = [10, 20, 30] +andika(20 ktk namba) // will print kweli +``` + +## Looping Over Arrays + +You can use the kwa and ktk keywords to loop over array elements. To loop over just the values, use the following syntax: + +``` +namba = [1, 2, 3, 4, 5] + +kwa thamani ktk namba { + andika(thamani) +} +``` + +To loop over both index and value pairs, use this syntax: + +```s +majina = ["Juma", "Asha", "Haruna"] + +kwa idx, jina ktk majina { + andika(idx, "-", jina) +} +``` + +## Array Methods + +Arrays in Nuru have several built-in methods: + +### idadi() + +idadi() returns the length of an array: + +```s +a = [1, 2, 3] +urefu = a.idadi() +andika(urefu) // will print 3 +``` + +### sukuma() + +sukuma() adds one or more items to the end of an array: + +```s +a = [1, 2, 3] +a.sukuma("s", "g") +andika(a) // will print [1, 2, 3, "s", "g"] +``` + +### yamwisho() + +yamwisho() returns the last item in an array, or tupu if the array is empty: + +```s +a = [1, 2, 3] +mwisho = a.yamwisho() +andika(mwisho) // will print 3 + +b = [] +mwisho = b.yamwisho() +andika(mwisho) // will print tupu +``` + +### map() + +map() goes through every element in the array and applies the passed function to each element. It will then return a new array with the updated elements: +```s +a = [1, 2, 3] + +b = a.map(unda(x){rudisha x*2}) + +andika(b) // [2, 4, 6] +``` + +### chuja() + +chuja() will go through every single element of an array and checks if that element returns true or false when passed into a function. It will return a new array with elements that returned true: +```s +a = [1, 2, 3, 4] + +b = a.chuja(unda(x){ + kama (x % 2 == 0) + {rudisha kweli} + rudisha sikweli + }) + +andika(b) // [2, 4] +``` + +With this information, you can now effectively work with arrays in Nuru, making it easy to manipulate collections of data in your programs. diff --git a/repl/docs/en/bool.md b/repl/docs/en/bool.md new file mode 100644 index 0000000..5df0764 --- /dev/null +++ b/repl/docs/en/bool.md @@ -0,0 +1,97 @@ +# Working with Booleans in Nuru + +Boolean objects in Nuru are truthy, meaning that any value is true, except tupu and sikweli. They are used to evaluate expressions that return true or false values. + +## Evaluating Boolean Expressions + +### Evaluating Simple Expressions + +In Nuru, you can evaluate simple expressions that return a boolean value: + +```s +andika(1 > 2) // Output: `sikweli` + +andika(1 + 3 < 10) // Output: `kweli` +``` + +### Evaluating Complex Expressions + +In Nuru, you can use boolean operators to evaluate complex expressions: + +```s +a = 5 +b = 10 +c = 15 + +result = (a < b) && (b < c) + +kama (result) { + andika("Both conditions are true") +} sivyo { + andika("At least one condition is false") +} +// Output: "Both conditions are true" +``` + +Here, we create three variables a, b, and c. We then evaluate the expression (a < b) && (b < c). Since both conditions are true, the output will be "Both conditions are true". + +## Boolean Operators + +Nuru has several boolean operators that you can use to evaluate expressions: + +### The && Operator + +The && operator evaluates to true only if both operands are true. Here's an example: + +```s +andika(kweli && kweli) // Output: `kweli` + +andika(kweli && sikweli) // Output: `sikweli` +``` + +### The || Operator + +The || operator evaluates to true if at least one of the operands is true. Here's an example: + +```s +andika(kweli || sikweli) // Output: `kweli` + +andika(sikweli || sikweli) // Output: `sikweli` +``` + +### The ! Operator + +The ! operator negates the value of the operand. Here's an example: + +```s +andika(!kweli) // Output: `sikweli` + +andika(!sikweli) // Output: `kweli` +``` + +## Working with Boolean Values in Loops + +In Nuru, you can use boolean expressions in loops to control their behavior. Here's an example: + +```s +namba = [1, 2, 3, 4, 5] + +kwa thamani ktk namba { + kama (thamani % 2 == 0) { + andika(thamani, "is even") + } sivyo { + andika(thamani, "is odd") + } +} +// Output: +// 1 is odd +// 2 is even +// 3 is odd +// 4 is even +// 5 is odd +``` + +Here, we create an array namba with the values 1 through 5. We then loop over each value in the array and use the % operator to determine if it is even or odd. The output will be "is even" for even numbers and "is odd" for odd numbers. + + +Boolean objects in Nuru can be used to evaluate expressions that return true or false values. You can use boolean operators to evaluate complex expressions and control the behavior of loops. Understanding how to work with boolean values is an essential skill for any Nuru programmer. \ No newline at end of file diff --git a/repl/docs/en/builtins.md b/repl/docs/en/builtins.md new file mode 100644 index 0000000..5c075ca --- /dev/null +++ b/repl/docs/en/builtins.md @@ -0,0 +1,45 @@ +# Built-in Functions in Nuru + +Nuru has several built-in functions that perform specific tasks. + +## The andika() Function + +The andika() function is used to print out messages to the console. It can take zero or more arguments, and the arguments will be printed out with a space in between them. Additionally, andika() supports basic formatting such as /n for a new line, /t for a tab space, and \\ for a backslash. Here's an example: + +```s +andika(1, 2, 3) // Output: "1 2 3" +``` + +## The jaza() Function + +The jaza() function is used to get input from the user. It can take zero or one argument, which is a string that will be used as a prompt for the user. Here's an example: + +```s +fanya salamu = unda() { + fanya jina = jaza("Unaitwa nani? ") + andika("Mambo vipi", jina) +} + +salamu() +``` + +In this example, we define a function `salamu()` that prompts the user to enter their name using the `jaza()` function. We then use the `andika()` function to print out a message that includes the user's name. + +## The aina() Function + +The `aina()` function is used to determine the type of an object. It accepts one argument, and the return value will be a string indicating the type of the object. Here's an example: + +```s +aina(2) // Output: "NAMBA" +aina("Nuru") // Output: "NENO" +``` + +## The fungua() Function + +The `fungua()` function is used to open a file. It accepts one argument, which is the path to the file that you want to open. Here's an example: + +```s +faili = fungua("data.txt") +``` + +In this example, we use the `fungua()` function to open a file named "data.txt". The variable faili will contain a reference to the opened file. \ No newline at end of file diff --git a/repl/docs/en/comments.md b/repl/docs/en/comments.md new file mode 100644 index 0000000..8e69b6b --- /dev/null +++ b/repl/docs/en/comments.md @@ -0,0 +1,30 @@ +# Comments in Nuru + +In Nuru, you can write comments to provide explanations and documentation for your code. Comments are lines of text that are ignored by the Nuru interpreter, so they will not affect the behavior of your program. There are two types of comments in Nuru: single-line comments and multi-line comments. + +## Single-Line Comments + +Single-line comments are used to provide brief explanations or documentation for a single line of code. To write a single-line comment in Nuru, use two forward slashes (//) followed by your comment text. Here's an example: + +```s +// This line will be ignored by the Nuru interpreter +``` + +In this example, the comment text "This line will be ignored by the Nuru interpreter" will be ignored by the interpreter, so it will not affect the behavior of the program. + +## Multi-Line Comments + +Multi-line comments are used to provide more detailed explanations or documentation for multiple lines of code. To write a multi-line comment in Nuru, use a forward slash followed by an asterisk ( /* ) to start the comment, and an asterisk followed by a forward slash ( */ ) to end the comment. Here's an example: + +```s +/* +These lines +Will +be +ignored +*/ +``` + +In this example, all the lines between the /* and */ symbols will be ignored by the Nuru interpreter, so they will not affect the behavior of the program. + +By utilizing single-line and multi-line comments in Nuru, you can make your code more readable and easier to maintain for yourself and others who may need to work with your code in the future. \ No newline at end of file diff --git a/repl/docs/en/dictionaries.md b/repl/docs/en/dictionaries.md new file mode 100644 index 0000000..e70b4ab --- /dev/null +++ b/repl/docs/en/dictionaries.md @@ -0,0 +1,107 @@ +# Dictionaries in Nuru + +Dictionaries in Nuru, also known as "kamusi," are powerful and flexible data structures that store key-value pairs. This page provides a comprehensive overview of dictionaries in Nuru, including how to create, access, modify, and iterate over them. + +## Creating Dictionaries + +Dictionaries are enclosed in curly braces {} and consist of keys and values separated by colons. Here's an example of defining a dictionary: + +```s + +orodha = {"jina": "Juma", "umri": 25} +``` + +Keys can be strings, integers, floats, or booleans, while values can be any data type, including strings, integers, floats, booleans, null, or functions: + +```s +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina value": tupu +} +``` + +## Accessing Elements + +Access individual elements in a dictionary using their keys: + +```s + +andika(k[kweli]) // kweli +andika(k["salimu"]("Juma")) // habari Juma +``` + +## Updating Elements + +Update the value of an element by assigning a new value to its key: + +```s +k['umri'] = 30 +andika(k['umri']) // 30 +``` + +## Adding New Elements + +Add a new key-value pair to a dictionary by assigning a value to a non-existent key: + +```s +k["lugha"] = "Kiswahili" +andika(k["lugha"]) // Kiswahili +``` + +## Concatenating Dictionaries + +Combine two dictionaries using the + operator: + +```s +matunda = {"a": "apple", "b": "banana"} +mboga = {"c": "carrot", "d": "daikon"} +vyakula = matunda + mboga +andika(vyakula) // {"a": "apple", "b": "banana", "c": "carrot", "d": "daikon"} +``` + +## Checking If a Key Exists in a Dictionary + +Use the ktk keyword to check if a key exists in a dictionary: + +```s + +"umri" ktk k // kweli +"urefu" ktk k // sikweli +``` + +## Looping Over a Dictionary + +Loop over a dictionary to access its keys and values: + +```s + +hobby = {"a": "asili", "b": "baiskeli", "c": "chakula"} +kwa i, v ktk hobby { + andika(i, "=>", v) +} +``` +Output +```s +a => asili +b => baiskeli +c => chakula +``` + +Loop over just the values: + +```s +kwa v ktk hobby { + andika(v) +} +``` +Output +```s +asili +baiskeli +chakula +``` + +With this knowledge, you can now effectively use dictionaries in Nuru to store and manage key-value pairs, offering a flexible way to organize and access data in your programs. \ No newline at end of file diff --git a/repl/docs/en/files.md b/repl/docs/en/files.md new file mode 100644 index 0000000..a31ef64 --- /dev/null +++ b/repl/docs/en/files.md @@ -0,0 +1,21 @@ +# Files in Nuru + +Nuru's ability to deal with files is primitive, and as for now it only allows you to read contents of a file. + +## Opening a File + +You open a file with the `fungua` keyword. This will return an object of type `FAILI`: +``` +fileYangu = fungua("file.txt") + +aina(fileYangu) // FAILI +``` + +## Reading a File + +Once you have a file object you can read its contents with the `soma()` method. This will return the contents of the file as a string: +``` +fileYangu = fungua("file.txt") + +fileYangu.soma() +``` diff --git a/repl/docs/en/for.md b/repl/docs/en/for.md new file mode 100644 index 0000000..d12ea51 --- /dev/null +++ b/repl/docs/en/for.md @@ -0,0 +1,175 @@ +# For Loops in Nuru + +For loops are a fundamental control structure in Nuru, used for iterating over iterable objects such as strings, arrays, and dictionaries. This page covers the syntax and usage of for loops in Nuru, including key-value pair iteration, and the use of break and continue statements. + +## Basic Syntax +To create a for loop, use the kwa keyword followed by a temporary identifier (such as i or v) and the iterable object. Enclose the loop body in curly braces {}. Here's an example with a string: + +```s +jina = "lugano" + +kwa i ktk jina { + andika(i) +} +``` +Output: + +```s +l +u +g +a +n +o +``` + +## Iterating Over Key-Value Pairs + +### Dictionaries + +Nuru allows you to iterate over both the value or the key-value pair of an iterable. To iterate over just the values, use one temporary identifier: + +```s +kamusi = {"a": "andaa", "b": "baba"} + +kwa v ktk kamusi { + andika(v) +} +``` + +Output: + +```s +andaa +baba +``` +To iterate over both the keys and the values, use two temporary identifiers: + +```s + +kwa k, v ktk kamusi { + andika(k + " ni " + v) +} +``` +Output: + +```s +a ni andaa +b ni baba +``` + +### Strings + +To iterate over just the values in a string, use one temporary identifier: + +```s +kwa v ktk "mojo" { + andika(v) +} +``` + +Output: +```s +m +o +j +o +``` +To iterate over both the keys and the values in a string, use two temporary identifiers: + +```s +kwa i, v ktk "mojo" { + andika(i, "->", v) +} +``` +Output: +```s +0 -> m +1 -> o +2 -> j +3 -> o +``` + +### Lists + +To iterate over just the values in a list, use one temporary identifier: + +```s +majina = ["juma", "asha", "haruna"] + +kwa v ktk majina { + andika(v) +} +``` + +Output: + +```s +juma +asha +haruna +``` + +To iterate over both the keys and the values in a list, use two temporary identifiers: + +```s +kwa i, v ktk majina { + andika(i, "-", v) +} +``` + +Output: + +```s +0 - juma +1 - asha +2 - haruna +``` + +## Break (Vunja) and Continue (Endelea) + +### Break (Vunja) + +Use the vunja keyword to terminate a loop: + +```s + +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimevunja") + vunja + } + andika(v) +} +``` + +Output: + +```s +m +o +nimevunja +``` + +### Continue (Endelea) + +Use the endelea keyword to skip a specific iteration: + +```s +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimeruka") + endelea + } + andika(v) +} +``` + +Output: + +```s +m +o +nimeruka +o +``` \ No newline at end of file diff --git a/repl/docs/en/function.md b/repl/docs/en/function.md new file mode 100644 index 0000000..cc70da4 --- /dev/null +++ b/repl/docs/en/function.md @@ -0,0 +1,97 @@ +# Functions in Nuru + +Functions are a fundamental part of Nuru programming, allowing you to define reusable blocks of code. This page covers the syntax and usage of functions in Nuru, including parameters, default parameters, return statements, recursion, and closures. + +## Basic Syntax + +A function block starts with the unda keyword, followed by parameters enclosed in parentheses () and the body enclosed in curly braces {}. Functions must be assigned to a variable: + +```s +jum = unda(x, y) { + rudisha x + y +} + +jum(2, 3) // 5 +``` + +## Parameters + +Functions can have zero or any number of arguments. Arguments can be of any type, even other functions: + +```s +salamu = unda() { + andika("Habari yako") +} + +salamu() + +salamu = unda(jina) { + andika("Habari yako", jina) +} + +salamu("asha") // Habari yako asha +``` + +## Default Parameters + +Functions can be provided with default parameters: + +```s +salimu = unda(salamu="Habari") { + andika(salamu) +} + +salimu() // Habari +salimu("Mambo") // Mambo +``` + +## Return (rudisha) + +You can return values with the rudisha keyword. The rudisha keyword will terminate the block and return the value: + +```s +mfano = unda(x) { + rudisha "nimerudi" + andika(x) +} + +mfano("x") // nimerudi +``` + +## Recursion + +Nuru also supports recursion. Here's an example of a recursive Fibonacci function: + +```s + +fib = unda(n) { + kama (n <= 1) { + rudisha n + } sivyo { + rudisha fib(n-1) + fib(n-2) + } +} + +andika(fib(10)) // 55 +``` + +The fib function calculates the nth Fibonacci number by recursively calling itself with n-1 and n-2 as arguments until n is less than or equal to 1. + +## Closures + +Closures are anonymous functions that can capture and store references to variables from their surrounding context. In Nuru, you can create closures using the unda keyword without assigning them to a variable. Here's an example: + +```s +fanya jum = unda(x) { + rudisha unda(y) { + rudisha x + y + } +} + +fanya jum_x = jum(5) +andika(jum_x(3)) // 8 +``` + +In the example above, the jum function returns another function that takes a single parameter y. The returned function has access to the x variable from its surrounding context. + +Now that you understand the basics of functions in Nuru, including recursion and closures, you can create reusable blocks of code to simplify your programs and improve code organization. \ No newline at end of file diff --git a/repl/docs/en/hisabati.md b/repl/docs/en/hisabati.md new file mode 100644 index 0000000..b8f4afb --- /dev/null +++ b/repl/docs/en/hisabati.md @@ -0,0 +1,257 @@ +# Module Hisabati + +Module Hisabati is a inbuilt math module by [VictorKariuki](https://github.com/VictorKariuki). + +This in-built module provides various mathematical functions and constants. It includes methods for `trigonometric functions`, `logarithmic functions`, `array operations`, and `utility functions`. + +## Usage + +To use the `hisabati` in-built module follow the steps below: + +1. You directly import the `hisabati` in-built module and any required in-built modules in your Nuru code using the `tumia` keyword. + + ```nuru + tumia hisabati + ``` + +2. Calling the in-built module methods: + + ```nuru + andika(hisabati.e()) + ``` + +## Yaliyomo + +This in-built module covers a wide range of mathematical operations, including : + +- `Basic Mathematical Functions:` +- `Hyperbolic` & `Trigonometric Functions` +- `Exponential` & `Logarithmic Functions` +- `Rounding` & `Comparison Functions` + +Here is an in-depth classification of the methods: + +1. Trigonometric Functions: + + - `cos(n)` + - `sin(n)` + - `tan(n)` + - `acos(n)` + - `asin(n)` + - `atan(n)` + - `hypot(numbers)` + +2. Hyperbolic Functions: + + - `cosh(n)` + - `sinh(n)` + - `tanh(n)` + - `acosh(n)` + - `asinh(n)` + - `atanh(n)` + +3. Exponential and Logarithmic Functions: + + - `exp(n)` + - `expm1(n)` + - `log(n)` + - `log2(n)` + - `log10(n)` + - `log1p(n)` + +4. Basic Mathematical Functions: + + - `abs(n)` + - `sqrt(n)` + - `cbrt(n)` + - `root(x, n)` + - `factorial(n)` + - `sign(n)` + +5. Rounding and Comparison Functions: + + - `ceil(n)` + - `floor(n)` + - `round(n)` + - `max(numbers)` + - `min(numbers)` + +### 1. Constants: + +- **PI**: Represents the mathematical constant `π`. +- **e**: Represents `Euler's Number`. +- **phi**: Represents the `Golden Ratio`. +- **ln10**: Represents the `natural logarithm of 10`. +- **ln2**: Represents the `natural logarithm of 2`. +- **log10e**: Represents the `base 10 logarithms` of Euler's number `(e)`. +- **log2e**: Represents the `base 2 logarithm` of Euler's number` (e)`. +- **sqrt1_2**: Represents the `square root` of `1/2`. +- **sqrt2**: Represents the `square root` of `2`. +- **sqrt3**: Represents the `square root` of `3`. +- **sqrt5**: Represents the `square root` of `5`. +- **EPSILON**: Represents a small value `2.220446049250313e-16`. + +### 2. Methods: + +1. **abs(namba)** + + - Description: Calculates the absolute value of a number. + - Example: `hisabati.abs(-42)` returns `42`. + +2. **acos(n)** + + - Description: Calculates the arccosine of a number. + - Example: `hisabati.acos(0.5)` returns `1.0471975511965979`. + +3. **acosh(n)** + + - Description: Calculates the inverse hyperbolic cosine of a number. + - Example: `hisabati.acosh(2.0)` returns `1.3169578969248166`. + +4. **asin(n)** + + - Description: Calculates the arcsine of a number using the Taylor series. + - Example: `hisabati.arcsin(0.5)` returns `0.5235987755982988`. + +5. **asinh(n)** + + - Description: Calculates the inverse hyperbolic sine of a number. + - Example: `hisabati.arsinh(2.0)` returns `1.4436354751788103`. + +6. **atan(n)** + + - Description: Calculates the arctangent of a number using the Taylor series. + - Example: `hisabati.atan(1.0)` returns `0.7853981633974483`. + +7. **atan2(y, x)** + + - Description: Calculates the arctangent of the quotient of its arguments. + - Example: `hisabati.atan2(1.0, 1.0)` returns `0.7853981633974483`. + +8. **atanh(n)** + + - Description: Calculates the inverse hyperbolic tangent of a number. + - Example: `hisabati.atanh(0.5)` returns `0.5493061443340549`. + +9. **cbrt(n)** + + - Description: Calculates the cube root of a number. + - Example: `hisabati.cbrt(8)` returns `2`. + +10. **root(x, n)** + + - Description: Calculates the nth root of a number using the Newton-Raphson method. + - Example: `hisabati.root(27, 3)` returns `3`. + +11. **ceil(n)** + + - Description: Rounds up to the smallest integer greater than or equal to a given number. + - Example: `hisabati.ceil(4.3)` returns `5`. + +12. **cos(n)** + + - Description: Calculates the cosine of an angle in radians using the Taylor series. + - Example: `hisabati.cos(0.0)` returns `1`. + +13. **cosh(n)** + + - Description: Calculates the hyperbolic cosine of a number. + - Example: `hisabati.cosh(0.0)` returns `1`. + +14. **exp(n)** + + - Description: Calculates the value of Euler's number raised to the power of a given number. + - Example: `hisabati.exp(2.0)` returns `7.38905609893065`. + +15. **expm1(n)** + + - Description: Calculates Euler's number raised to the power of a number minus 1. + - Example: `hisabati.expm1(1.0)` returns `1.718281828459045`. + +16. **floor(n)** + + - Description: Rounds down to the largest integer less than or equal to a given number. + - Example: `hisabati.floor(4.7)` returns `4`. + +17. **hypot(values)** + + - Description: Calculates the square root of the sum of squares of the given values. + - Example: `hisabati.hypot([3, 4])` returns `5`. + +18. **log(n)** + + - Description: Calculates the natural logarithm of a number. + - Example: `hisabati.log(1.0)` returns `0`. + +19. **log10(n)** + + - Description: Calculates the base 10 logarithm of a number. + - Example: `hisabati.log10(100.0)` returns `2`. + +20. **log1p(n)** + + - Description: Calculates the natural logarithm of 1 plus the given number. + - Example: `hisabati.log1p(1.0)` returns `0.6931471805599453`. + +21. **log2(n)** + + - Description: Calculates the base 2 logarithm of a number. + - Example: `hisabati.log2(8)` returns `3`. + +22. **max(numbers)** + + - Description: Finds the maximum value in a list of numbers. + - Example: `hisabati.max([4, 2, 9, 5])` returns `9`. + +23. **min(numbers)** + + - Description: Finds the minimum value in a list of numbers. + - Example: `hisabati.min([4, 2, 9, 5])` returns `2`. + +24. **round(x, method)** + + - Description: Rounds a number to the nearest integer using the specified method. + - Example: `hisabati.round(4.6)` returns `5`. + +25. **sign(n)** + + - Description: Determines the sign of a number. + - Example: `hisabati.sign(-5)` returns `-1`. + +26. **sin(n)** + + - Description: Calculates the sine of an angle in radians using the Taylor series. + - Example: `hisabati.sin(1.0)` returns `0.8414709848078965`. + +27. **sinh(n)** + + - Description: Calculates the hyperbolic sine of a number. + - Example: `hisabati.sinh(1.0)` returns `1.1752011936438014`. + +28. **sqrt(n)** + + - Description: Calculates the square root of a number. + - Example: `hisabati.sqrt(4)` returns `2`. + +29. **tan(n)** + + - Description: Calculates the tangent of an angle in radians. + - Example: `hisabati.tan(1.0)` returns `1.557407724654902`. + +30. **tanh(n)** + + - Description: Calculates the hyperbolic tangent of a number. + - Example: `hisabati.tanh(1.0)` returns `0.7615941559557649`. + +31. **factorial(n)** + + - Description: Calculates the factorial of a number. + - Example: `hisabati.factorial(5)` returns `120`. + +### Contributing + +Contributions to the `module hisabati` are welcome. If you have any improvements or bug fixes, feel free to create a pull request. + +### License + +This in-built module is available under the MIT License. See the [LICENSE](LICENSE) file for more information. \ No newline at end of file diff --git a/repl/docs/en/identifiers.md b/repl/docs/en/identifiers.md new file mode 100644 index 0000000..0f94880 --- /dev/null +++ b/repl/docs/en/identifiers.md @@ -0,0 +1,31 @@ +# Identifiers in Nuru + +Identifiers are used to name variables, functions, and other elements in your Nuru code. This page covers the rules and best practices for creating identifiers in Nuru. + +## Syntax Rules + +Identifiers can contain letters, numbers, and underscores. However, there are a few rules you must follow when creating identifiers: +- Identifiers cannot start with a number. +- Identifiers are case-sensitive. For example, myVar and myvar are considered distinct identifiers. + +Here are some examples of valid identifiers: + +```s +fanya birth_year = 2020 +andika(birth_year) // 2020 + +fanya convert_c_to_p = "C to P" +andika(convert_c_to_p) // "C to P" +``` + +In the examples above, birth_year and convert_c_to_p are both valid identifiers. + +## Best Practices + +When choosing identifiers, it's important to follow best practices to ensure your code is clear and easy to understand: + +- Use descriptive names that clearly indicate the purpose or meaning of the variable or function. +- Follow a consistent naming convention, such as camelCase (myVariableName) or snake_case (my_variable_name). +- Avoid using single-letter variable names, except for commonly accepted cases like loop counters (i, j, k). + +By following these best practices when creating identifiers, you will make your Nuru code more readable and maintainable for yourself and others. diff --git a/repl/docs/en/ifStatements.md b/repl/docs/en/ifStatements.md new file mode 100644 index 0000000..5e74560 --- /dev/null +++ b/repl/docs/en/ifStatements.md @@ -0,0 +1,38 @@ +# Conditional Statements in Nuru + +Conditional statements in Nuru are used to perform different actions based on different conditions. The if/else statement is a fundamental control structure that allows you to execute code based on specific conditions. This page covers the basics of if/else statements in Nuru. + +## If Statement (Kama) + +An if statement starts with the kama keyword, followed by a condition in parentheses (). If the condition is true, the code inside the curly braces {} will be executed. + +```s +kama (2 > 1) { + andika(kweli) // kweli +} +``` + +In this example, the condition 2 > 1 is true, so the andika(kweli) statement is executed, and the output is kweli. + +## Else If and Else Blocks (Au Kama and Sivyo) + +You can use au kama to test multiple conditions and sivyo to specify a default block of code to be executed when none of the conditions are true. + +```s + +fanya a = 10 + +kama (a > 100) { + andika("a imezidi 100") +} au kama (a < 10) { + andika("a ndogo kuliko 10") +} sivyo { + andika("Thamani ya a ni", a) +} + +// The output will be 'Thamani ya a ni 10' +``` + +In this example, the first condition a > 100 is false, and the second condition a < 10 is also false. Therefore, the code inside the sivyo block is executed, and the output is 'Thamani ya a ni 10'. + +By using if/else statements with the kama, au kama, and sivyo keywords, you can control the flow of your Nuru code based on different conditions. \ No newline at end of file diff --git a/repl/docs/en/json.md b/repl/docs/en/json.md new file mode 100644 index 0000000..948abd6 --- /dev/null +++ b/repl/docs/en/json.md @@ -0,0 +1,43 @@ +# JSON in Nuru + +Nuru also makes it easy to deal with JSON. + +## Import JSONI + +Use the following to import the json module: +``` +tumia jsoni +``` + +## Decoding JSON with dikodi() +Use this to convert a string to a dictionary: +``` +jsonString = '{ + "error": false, + "category": "Pun", + "type": "single", + "joke": "I was reading a great book about an immortal dog the other day. It was impossible to put down." +}' + +// to make it a dict + +tumia jsoni + +k = jsoni.dikodi(jsonString) + +k["joke"] // I was reading a great book about an immortal dog the other day. It was impossible to put down. +``` + +## Encoding JSON with enkodi() + +You can encode JSON with the `enkodi` method, this will turn a dictionary to a string: +``` +tumia jsoni + +k = { + "a": "apple", + "b": "banana" + } + +j = json.enkodi(k) +``` diff --git a/repl/docs/en/keywords.md b/repl/docs/en/keywords.md new file mode 100644 index 0000000..c94365a --- /dev/null +++ b/repl/docs/en/keywords.md @@ -0,0 +1,59 @@ +# KEYWORDS + +Keywords in Nuru are reserved words that have special meanings and cannot be used as identifiers for variables, functions, or classes. This page covers the syntax and usage of keywords in Nuru, including reserved keywords and built-in functions. + +## Reserved Keywords + +The table below lists the reserved keywords in Nuru. These words have specific meanings in the language and cannot be used as identifiers: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
kwelisikweliundafanya
kamaausivyowakati
rudishavunjaendeleatupu
ktkkwabadiliikiwa
kawaida
+ +## BuiltIns + +Nuru also provides several built-in functions that are reserved and cannot be used as identifiers. These functions offer essential functionalities for common tasks in the language: + + + + + + + + + + +
andikaainajazafungua
+ +Understanding the keywords and built-in functions in Nuru is essential for writing clear, concise, and error-free code. By respecting the reserved words and their specific meanings, you can create more robust and maintainable programs in Nuru. \ No newline at end of file diff --git a/repl/docs/en/net.md b/repl/docs/en/net.md new file mode 100644 index 0000000..3e30399 --- /dev/null +++ b/repl/docs/en/net.md @@ -0,0 +1,47 @@ +# HTTP with Nuru + +You can access the internet via http protocol using the `mtandao` module. + +## Importing + +Import the module with: +``` +tumia mtandao +``` + +## Methods + +### peruzi() + +Use this as GET method. It can either accept one positional argument which will be the URL: + +``` +tumia mtandao + +mtandao.peruzi("http://google.com") +``` + +Or you can use keyword arguments to pass in parameters and headers as shown below. Note that headers and parameters must be a dictionary: + +``` +tumia mtandao + +url = "http://mysite.com" +headers = {"Authentication": "Bearer XXXX"} + +mtandao.peruzi(yuareli=url, vichwa=headers, mwili=params) +``` + +### tuma() + +Use this as POST method. Use keyword arguments to pass in parameters and headers as shown below. Note that headers and parameters must be a dictionary: + +``` +tumia mtandao + +url = "http://mysite.com" +headers = {"Authentication": "Bearer XXXX"} +params = {"key": "Value"} + +mtandao.tuma(yuareli=url, vichwa=headers, mwili=params) +``` diff --git a/repl/docs/en/null.md b/repl/docs/en/null.md new file mode 100644 index 0000000..75d8883 --- /dev/null +++ b/repl/docs/en/null.md @@ -0,0 +1,26 @@ +# Null (Tupu) in Nuru + +The null data type in Nuru represents the absence of a value or the concept of "nothing" or "empty." This page covers the syntax and usage of the null data type in Nuru, including its definition and evaluation. + +## Definition + +A null data type is a data type with no value, defined with the tupu keyword: + +```s +fanya a = tupu +``` +## Evaluation + +When evaluating a null data type in a conditional expression, it will evaluate to false: + +```s +kama (a) { + andika("niko tupu") +} sivyo { + andika("nimevaa nguo") +} + +// Output: nimevaa nguo +``` + +The null data type is useful in Nuru when you need to represent an uninitialized, missing, or undefined value in your programs. By understanding the null data type, you can create more robust and flexible code. \ No newline at end of file diff --git a/repl/docs/en/numbers.md b/repl/docs/en/numbers.md new file mode 100644 index 0000000..3133534 --- /dev/null +++ b/repl/docs/en/numbers.md @@ -0,0 +1,76 @@ +# INTEGERS (NAMBA) AND FLOATS (DESIMALI) + +Integers and floats are the basic numeric data types in Nuru, used for representing whole numbers and decimal numbers, respectively. This page covers the syntax and usage of integers and floats in Nuru, including precedence, unary increments, shorthand assignments, and negative numbers. + +## PRECEDENCE + +Integers and floats behave as expected in mathematical operations, following the BODMAS rule: +```go +2 + 3 * 5 // 17 + +fanya a = 2.5 +fanya b = 3/5 + +a + b // 2.8 +``` + +## UNARY INCREMENTS + +You can perform unary increments (++ and --) on both floats and integers. These will add or subtract 1 from the current value. Note that the float or int have to be assigned to a variable for this operation to work. Here's an example: + +```go +fanya i = 2.4 + +i++ // 3.4 +``` + +## SHORTHAND ASSIGNMENT + +Nuru supports shorthand assignments with +=, -=, /=, *=, and %=: +You +```go +fanya i = 2 + +i *= 3 // 6 +i /= 2 // 3 +i += 100 // 103 +i -= 10 // 93 +i %= 90 // 3 +``` + +## NEGATIVE NUMBERS + +Negative numbers also behave as expected: + +```go +fanya i = -10 + +wakati (i < 0) { + andika(i) + i++ +} + +``` +Output: +```s +-10 +-9 +-8 +-7 +-6 +-5 +-4 +-3 +-2 +-1 +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +``` diff --git a/repl/docs/en/operators.md b/repl/docs/en/operators.md new file mode 100644 index 0000000..49eef51 --- /dev/null +++ b/repl/docs/en/operators.md @@ -0,0 +1,76 @@ +# OPERATORS +Operators are the foundation of any programming language, allowing you to perform various operations on variables and values. This page covers the syntax and usage of operators in Nuru, including assignment, arithmetic, comparison, member, and logic operators. + +## ASSIGNMENT + +Assuming `i` and `v` are predefined variables, Nuru supports the following assignment operators: + +- `i = v`: which is the regular assignment operator +- `i += v`: which is the equivalent of `i = i + v` +- `i -= v`: which is the equivalent of `i = i - v` +- `i *= v`: which is the equivalent of `i = i * v` +- `i /= v`: which is the equivalent of `i = i / v` +- `i += v`: which is the equivalent of `i = i + v` + +For `strings`, `arrays` and `dictionaries`, the `+=` sign operator is permissible. Example: +``` +list1 += list2 // this is equivalent to list1 = list1 + list2 +``` + +## ARITHMETIC OPERATORS + +Nuru supports the following arithmetic operators: + +- `+`: Additon +- `-`: Subtraction +- `*`: Multiplication +- `/`: Division +- `%`: Modulo (ie the remainder of a division) +- `**`: Exponential power (eg: `2**3 = 8`) + +## COMPARISON OPERATORS + +Nuru supports the following comparison operators: + +- `==`: Equal to +- `!=`: Not equal to +- `>`: Greater than +- `>=`: Greater than or equal to +- `<`: Less than +- `<=`: Less than or equal to + +## MEMBER OPERATOR + +The member operator in Nuru is `ktk`. It will check if an object exists in another object: +```go +fanya majina = ['juma', 'asha', 'haruna'] + +"haruna" ktk majina // kweli +"halima" ktk majina // sikweli +``` + +## LOGIC OPERATORS + +Nuru supports the following logic operators: + +- `&&`: Logical `AND`. It will evaluate to true if both are true, otherwise it will evaluate to false. +- `||`: Logical `OR`. It will evaluate to false if both are false, otherwise it will evaluate to true. +- `!`: Logical `NOT`. It will evaluate to the opposite of a given expression. + +## PRECEDENCE OF OPERATORS + +Operators have the following precedence, starting from the highest priority to the lowest: + +- `()` : Items in paranthesis have the highest priority +- `!`: Negation +- `%`: Modulo +- `**`: Exponential power +- `/, *`: Division and Multiplication +- `+, +=, -, -=`: Addition and Subtraction +- `>, >=, <, <=`: Comparison operators +- `==, !=`: Equal or Not Equal to +- `=`: Assignment Operator +- `ktk`: Member Operator +- `&&, ||`: Logical AND and OR + +Understanding operators in Nuru allows you to create complex expressions, perform calculations, and make decisions based on the values of variables. diff --git a/repl/docs/en/packages.md b/repl/docs/en/packages.md new file mode 100644 index 0000000..865bf87 --- /dev/null +++ b/repl/docs/en/packages.md @@ -0,0 +1,76 @@ +# Packages in Nuru + +You can use third packages written in Nuru with the following conditions: + +- The package file MUST be in the same directory +- The package file MUST end with `nr` +- The package name and package file MUST have the same name (eg: if `pakeji hesabu` then the file name must be `hesabu.nr`) +- The package must have the following structure: +``` +// imports if any + +pakeji [name of package] { + andaa = unda() { // the andaa function is mandatory even if its empty + + } + [body of package] + } +``` +- The package must be initialized with the `andaa` keyword (see above). + +The `andaa` keyword is for initializing your package. This is also where you'd put your global variables. The global variables should be prefixed with `@.` Eg: `@.myGlobalVar`. + +A variable being globally available means that the variable can be accessed and manipulated by all other methods in the package. + + +Below is an example Sarufi package: +``` +// import modules +tumia mtandao +tumia jsoni + +// package body +pakeji sarufi { + + // initialize function + andaa = unda(file) { + config = fungua(file) // read passwords from json file + configString = config.soma() + + configDict = jsoni.dikodi(configString) // convert it to a dict + clientID = configDict["client_id"] + clientSecret = configDict["client_secret"] + + // fill in params + params = {"client_id": clientID, "client_secret": clientSecret} + + // get response + resp = mtandao.tuma(yuareli="https://api.sarufi.io/api/access_token", mwili=params) + tokenDict = jsoni.dikodi(resp) + + // extract token and make it globally available + @.token = tokenDict["access_token"] + + // make the "Bearer " globally available + @.Auth = "Bearer " + @.token + } + + // a method to get token + tokenYangu = unda() { + rudisha @.token + } + + // a method to create new chatbots. + // the data should be a dict + tengenezaChatbot = unda(data) { + majibu = mtandao.tuma(yuareli="https://api.sarufi.io/chatbot", vichwa={"Authorization": @.Auth}, mwili = data) + rudisha majibu + } + + // a method to get all available chatbots + pataChatbotZote = unda() { + majibu = mtandao.peruzi(yuareli="https://api.sarufi.io/chatbots", vichwa={"Authorization": @.Auth}) + rudisha majibu + } + } +``` diff --git a/repl/docs/en/range.md b/repl/docs/en/range.md new file mode 100644 index 0000000..32d6eac --- /dev/null +++ b/repl/docs/en/range.md @@ -0,0 +1,55 @@ +## Range Function (mfululizo) + +The `mfululizo` function generates a sequence of numbers. It can be used in loops or to create arrays of sequential numbers. + +### Syntax + +```go +mfululizo(end) +mfululizo(start, end) +mfululizo(start, end, step) +``` + +### Parameters + +- `end`: The upper limit of the sequence (exclusive). +- `start` (optional): The starting value of the sequence. Default is 0. +- `step` (optional): The increment between each number in the sequence. Default is 1. + +### Return Value + +Returns an array of integers. + +### Examples + +```go +// Generate numbers from 0 to 4 +kwa i katika mfululizo(5) { + andika(i) +} +// Output: 0 1 2 3 4 + +// Generate numbers from 1 to 9 +kwa i katika mfululizo(1, 10) { + andika(i) +} +// Output: 1 2 3 4 5 6 7 8 9 + +// Generate even numbers from 0 to 8 +kwa i katika mfululizo(0, 10, 2) { + andika(i) +} +// Output: 0 2 4 6 8 + +// Generate numbers in reverse order +kwa i katika mfululizo(10, 0, -1) { + andika(i) +} +// Output: 10 9 8 7 6 5 4 3 2 1 +``` + +### Notes + +- The `end` value is exclusive, meaning the sequence will stop before reaching this value. +- If a negative `step` is provided, `start` should be greater than `end`. +- The `step` value cannot be zero. diff --git a/repl/docs/en/strings.md b/repl/docs/en/strings.md new file mode 100644 index 0000000..4132953 --- /dev/null +++ b/repl/docs/en/strings.md @@ -0,0 +1,148 @@ +# Strings in Nuru + +Strings are a sequence of characters that can represent text in the Nuru programming language. This page covers the basics of strings, their manipulation, and some built-in methods. + +## Basic Syntax + +Strings can be enclosed in either single quotes '' or double quotes "": + +```s +andika("mambo") // mambo + +fanya a = 'niaje' + +andika("mambo", a) // mambo niaje +``` + +## Concatenating Strings + +Strings can be concatenated using the + operator: + +```s +fanya a = "habari" + " " + "yako" + +andika(a) // habari yako + +fanya b = "habari" + +b += " yako" + +// habari yako +``` + +You can also repeat a string n number of times using the * operator: + +```s +andika("mambo " * 4) + +// mambo mambo mambo mambo + +fanya a = "habari" + +a *= 4 + +// habarihabarihabarihabari +``` + +## Looping over a String + +You can loop through a string using the kwa keyword: + +```s +fanya jina = "avicenna" + +kwa i ktk jina {andika(i)} +``` +Output +```s +a +v +i +c +e +n +n +a +``` + +And for key-value pairs: + +```s +kwa i, v ktk jina { + andika(i, "=>", v) +} +``` +Output +```s +0 => a +1 => v +2 => i +3 => c +4 => e +5 => n +6 => n +7 => a +``` + +## Comparing Strings + +You can compare two strings using the == operator: + +```s +fanya a = "nuru" + +andika(a == "nuru") // kweli + +andika(a == "mambo") // sikweli +``` + +## String Methods + +### idadi() + +You can find the length of a string using the idadi method. It does not accept any parameters. + +```s +fanya a = "mambo" +a.idadi() // 5 +``` + +### herufikubwa() + +This method converts a string to uppercase. It does not accept any parameters. + +```s +fanya a = "nuru" +a.herufikubwa() // NURU +``` + +### herufindogo + +This method converts a string to lowercase. It does not accept any parameters. + +```s +fanya a = "NURU" +a.herufindogo() // nuru +``` + +### gawa + +The gawa method splits a string into an array based on a specified delimiter. If no argument is provided, it will split the string according to whitespace. + +Example without a parameter: + +```s +fanya a = "nuru mambo habari" +fanya b = a.gawa() +andika(b) // ["nuru", "mambo", "habari"] +``` + +Example with a parameter: + +```s +fanya a = "nuru,mambo,habari" +fanya b = a.gawa(",") +andika(b) // ["nuru", "mambo", "habari"] +``` + +By understanding strings and their manipulation in Nuru, you can effectively work with text data in your programs. \ No newline at end of file diff --git a/repl/docs/en/switch.md b/repl/docs/en/switch.md new file mode 100644 index 0000000..6286429 --- /dev/null +++ b/repl/docs/en/switch.md @@ -0,0 +1,59 @@ +# Switch Statements in Nuru + +Switch statements in Nuru allow you to execute different code blocks based on the value of a given expression. This page covers the basics of switch statements and their usage. + +## Basic Syntax + +You initialize a switch statement with the badili keyword, the expression inside parentheses (), and all cases enclosed within curly braces {}. + +A case statement has the keyword ikiwa followed by a value to check. Multiple values can be in a single case separated by commas ,. The consequence to execute if a condition is fulfilled must be inside curly braces {}. Here's an example: + +```s +fanya a = 2 + +badili (a){ + ikiwa 3 { + andika("a ni tatu") + } + ikiwa 2 { + andika ("a ni mbili") + } +} +``` + +## Multiple Values in a Case + +Multiple possibilities can be assigned to a single case (ikiwa) statement: + +```s +badili (a) { + ikiwa 1,2,3 { + andika("a ni kati ya 1, 2 au 3") + } + ikiwa 4 { + andika("a ni 4") + } +} +``` + +## Default Case (kawaida) + +The default statement will be executed when no condition is satisfied. The default statement is represented by kawaida: + +```s +fanya z = 20 + +badili(z) { + ikiwa 10 { + andika("kumi") + } + ikiwa 30 { + andika("thelathini") + } + kawaida { + andika("ishirini") + } +} +``` + +By understanding switch statements in Nuru, you can create more efficient and organized code that can handle multiple conditions easily. \ No newline at end of file diff --git a/repl/docs/en/time.md b/repl/docs/en/time.md new file mode 100644 index 0000000..7809afe --- /dev/null +++ b/repl/docs/en/time.md @@ -0,0 +1,102 @@ +# Time in Nuru + +## Importing Time + +To use Time in Nuru, you first have to import the `muda` module as follows: + +```so +tumia muda +``` + +--- + +## Time Methods + +### `hasahivi()` + +To get the current time, use `muda.hasahivi()`. It returns a `muda` object with the current time in the format `HH:mm:ss dd-MM-YYYY`. + +```so +tumia muda + +saivi = muda.hasahivi() +``` + +--- + +### `leo()` + +To get today’s date in the format `dd-MM-YYYY`: + +```so +tumia muda + +leo = muda.leo() +``` + +--- + +### `tangu(time)` + +Gets the total time elapsed **in seconds** from the given time to now. Accepts a `muda` object or string in `HH:mm:ss dd-MM-YYYY` format. + +```so +tumia muda + +muda_ulioyopita = muda.tangu("15:00:00 01-01-2024") +``` + +--- + +### `lala(sekunde)` + +Pauses the program for the given number of seconds: + +```so +tumia muda + +muda.lala(5) // sleeps for 5 seconds +``` + +--- + +### `baada_ya(sekunde)` + +Returns a `muda` object representing the time after the given number of seconds from now. + +```so +tumia muda + +baadaye = muda.baada_ya(60) // one minute from now +``` + +--- + +### `tofauti(muda1, muda2)` + +Returns the difference between two time values in seconds. + +```so +tumia muda + +saa1 = muda.hasahivi() +saa2 = muda.baada_ya(30) + +tofauti = muda.tofauti(saa2, saa1) // 30 +``` + +--- + +### `ongeza(...)` + +To add time to a `muda` object. You must specify at least one of the following fields `sekunde`, `dakika`, `masaa`, `siku`, `wiki`, `miezi`, `miaka`. + +Example: + +```so +tumia muda + +sasa = muda.hasahivi() +kesho = sasa.ongeza(siku=1) +mwakani = sasa.ongeza(miaka=1) +``` diff --git a/repl/docs/en/while.md b/repl/docs/en/while.md new file mode 100644 index 0000000..45972c0 --- /dev/null +++ b/repl/docs/en/while.md @@ -0,0 +1,74 @@ +# WHILE (WAKATI) + +While loops in Nuru are used to execute a block of code repeatedly, as long as a given condition is true. This page covers the basics of while loops, including how to use the break and continue keywords within them. + +## Basic Syntax + +A while loop is executed when a specified condition is true. You initiliaze a while loop with the `wakati` keyword followed by the condition in paranthesis `()`. The consequence of the loop should be enclosed in brackets `{}`: +```s +fanya i = 1 + +wakati (i <= 5) { + andika(i) + i++ +} +``` +Output +```s +1 +2 +3 +4 +5 +``` + +## Break (vunja) and Continue (endelea) +### Break (Vunja) + +Use the vunja keyword to terminate a loop: + +```s +fanya i = 1 + +wakati (i < 5) { + kama (i == 3) { + andika("nimevunja") + vunja + } + andika(i) + i++ +} +``` +Output +```s +1 +2 +nimevunja +``` + +### Continue (Endelea) + +Use the endelea keyword to skip a specific iteration: + +```s +fanya i = 0 + +wakati (i < 5) { + i++ + kama (i == 3) { + andika("nimeruka") + endelea + } + andika(i) +} +``` +Output +```s +1 +2 +nimeruka +4 +5 +``` + +By understanding while loops in Nuru, you can create code that repeats a specific action or checks for certain conditions, offering more flexibility and control over your code execution. \ No newline at end of file diff --git a/repl/docs/sw/README.md b/repl/docs/sw/README.md new file mode 100644 index 0000000..02d28f0 --- /dev/null +++ b/repl/docs/sw/README.md @@ -0,0 +1,27 @@ +# NYARAKA YA LUGHA YA PROGRAMU YA NURU + +Hii nyaraka imeandikwa ikilenga watu wenye uzoefu na kuandika au kupanga programu. Inaelezea sintaksia, aina na namna ya kufanya operesheni mbali mbali kutumia lugha ya NURU. + +## Yaliyomo + +- [Safu Kwenye Nuru](arrays.md#arrays-in-nuru) + - [Kutengeneza Safu](arrays.md#creating-arrays) + - [Kupata na Kurekebisha vipengele vya Safu](arrays.md#accessing-and-modifying-array-elements) + - [Kuunganisha Safu](arrays.md#concatenating-arrays) + - [Kuangalia uwepo wa vipengele ndani ya safu](arrays.md#checking-for-array-membership) + - [Kupita na Kurejea kwenye safu](arrays.md#looping-over-arrays) + - [Mbinu za Safu](arrays.md#array-methods) + - [idadi()](arrays.md#idadi()) + - [sukuma()](arrays.md#sukuma()) + - [yamwisho()](arrays.md#yamwisho()) +- [Visaidia-kazi vya Nuru](builtins.md#built-in-functions-in-nuru) + - [Kisaidia-kazi andika() ](builtins.md#the-andika()-function) + - [Kisaidia-kazi jaza()](builtins.md#the-jaza()-function) + - [Kisaidia-kazi aina()](builtins.md#the-aina()-function) + - [Kisaidia-kazi fungua()](builtins.md#the-fungua()-function) +- [Maoni kwenye Nuru](comments.md#comments-in-nuru) + - [Maoni ya mstari mmoja](comments.md#single-line-comments) + - [Maoni ya mistari mingi](comments.md#multi-line-comments) +- [Kauli za masharti kwenye Nuru](ifStatements.md#conditional-statements-in-nuru) + - [Kauli ya kama](ifStatements.md#if-statement-(kama)) + - [Kauli za Au Kama na Sivyo)](ifStatements.md#else-if-and-else-blocks-(au-kama-and-sivyo)) \ No newline at end of file diff --git a/repl/docs/sw/arrays.md b/repl/docs/sw/arrays.md new file mode 100644 index 0000000..d9fbc6b --- /dev/null +++ b/repl/docs/sw/arrays.md @@ -0,0 +1,122 @@ +# Orodha Au Safu Katika Nuru + +Safu katika nuru ni miundo ya data ambayo inaweza kubeba vitu vingi, ikiwa ni pamoja na aina za data tofauti tofauti kama `namba`, `tungo`, `buliani`, `vitendakazi`, na thamani `tupu`. Ukurasa huu unaangazia vipengele mbalimbali vya safu, ikiwemo namna ya kutengeneza, kuchambua, na kuzunguka ndani yake kwa kutumia vitendakazi vilivyojengwa ndani ya Nuru. + +## Kutengeneza Safu + +Kutengeneza safu, tumia mabano mraba na tenganisha kila kitu kimoja kwa kutumia mkwaju: + +```s +orodha = [1, "pili", kweli] +``` + +## Kupata na Kubadilisha Vipengele vya Safu + +Safu katika Nuru ni zero-indexed; ikimaanisha kipengele cha kwanza katika safu kina kumbukumbu namba 0. Kupata kipengele, unaweza ukatumia kumbukumbu namba yake ndani ya mabano mraba: + +```s +namba = [10, 20, 30] +jina = namba[1] // jina is 20 +``` + +Unaweza ukabadilisha kipengele katika safu kwa kutumia kumbukumbu namba yake: + +```s +namba = [10, 20, 30] +namba[1] = 25 +andika(namba) // Tokeo: [10,25,30] +``` + +## Kuunganisha Safu + +Kuunganisha safu mbili au zaidi, tumia kiendeshi `+`: + +```s +a = [1, 2, 3] +b = [4, 5, 6] +c = a + b +// c is now [1, 2, 3, 4, 5, 6] +``` + +## Kuangalia Uanachama Katika Safu + +Tumia neno msingi `ktk` kuangalia kama kipengele kipo ndani ya safu: + +```s +namba = [10, 20, 30] +andika(20 ktk namba) // Tokeo: kweli +``` + +## Kuzunguka Ndani ya Safu + +Unaweza kutumia maneno msingi `kwa` na `ktk` kuzunguka ndani ya safu. Kuzunguka ndani ya safu na kupata kipengele peke yake tumia sintaksia ifuatayo: + +```s +namba = [1, 2, 3, 4, 5] + +kwa thamani ktk namba { + andika(thamani) +} + +//Tokeo: +1 +2 +3 +4 +5 +``` + +Kuzunguka ndani ya safu na kupata kumbukumbu namba na kipengele tumia sintaksi aifuatayo: + +```s +majina = ["Juma", "Asha", "Haruna"] + +kwa idx, jina ktk majina { + andika(idx, "-", jina) +} + +//Tokeo: +0-Juma +1-Asha +2-Haruna +``` + +## Vitendakazi vya Safu + +Nuru ina vitendakazi mbalimbali vilivyojengwa ndani kwa ajili ya Safu: + +### idadi() + +`idadi()` hurudisha urefu wa safu: + +```s +a = [1, 2, 3] +urefu = a.idadi() +andika(urefu) // Tokeo: 3 +``` + +### sukuma() + +`sukuma()` huongeza kipengele kimoja au zaidi mwishoni mwa safu: + +```s +a = [1, 2, 3] +a.sukuma("s", "g") +andika(a) // Tokeo [1, 2, 3, "s", "g"] +``` + +### yamwisho() + +`yamwisho()` hurudisha kipengele cha mwisho katika safu, au `tupu` kama safu haina kitu: + +```s +a = [1, 2, 3] +mwisho = a.yamwisho() +andika(mwisho) // Tokeo: 3 + +b = [] +mwisho = b.yamwisho() +andika(mwisho) // Tokeo: tupu +``` + +Kwa kutumia taarifa hii, unaweza ukafanyakazi na safu za Nuru kwa ufanisi, kufanya iwe rahisi kuchambua mikusanyo ya data katika programu zako. diff --git a/repl/docs/sw/bools.md b/repl/docs/sw/bools.md new file mode 100644 index 0000000..a43a5a2 --- /dev/null +++ b/repl/docs/sw/bools.md @@ -0,0 +1,97 @@ +# Kufanya Kazi na Buliani Katika Nuru + +Vitu vyote katika Nuru ni kweli, yaani thamani yoyote ni kweli isipokua tupu and sikweli. Hutumika kutathmini semi ambazo zinarudisha kweli au sikweli. + +## Kutathmini Semi za Buliani + +### Kutathmini Semi Rahisi + +Katika Nuru, unaweza kutathmini semi rahisi zinazorudisha thamani ya buliani: + +```go +andika(1 > 2) // Matokeo: `sikweli` + +andika(1 + 3 < 10) // Matokeo: `kweli` +``` + +### Kutathmini Semi Tata + +Katika Nuru, unaweza kutumia viendeshaji vya buliani kutathmini semi tata: + +```go +a = 5 +b = 10 +c = 15 + +tokeo = (a < b) && (b < c) + +kama (tokeo) { + andika("Hali zote mbili ni kweli") +} sivyo { + andika("Angalau hali moja ni sikweli") +} +// Tokeo: "Hali zote mbili ni kweli" +``` + +Hapa tumetengeneza vibadilika vitatu a,b,c. Kisha tukatathmini semi (a < b) && (b < c). Kwa sababu semi zote mbili ni kweli, tokeo litakua "Hali zote mbili ni kweli". + +## Vitendakazi vya Buliani + +Nuru ina vitendakazi vya buliani kadhaa ambavyo unaweza ukatumia kutathmini semi: + +### Kitendakazi `&&` + +Kitendakazi `&&` hutathmini kwenda kweli kama tu vitu vyote vinavyohusika ni kweli. Kwa mfano: + +```go +andika(kweli && kweli) // Tokeo: `kweli` + +andika(kweli && sikweli) // Tokeo: `sikweli` +``` + +### Kitendakazi `||` + +Kitendakazi || hutathmini kwenda kweli kama angalau kitu kimoja kati ya vyote vinavyohusika ni kweli. Kwa mfano: + +```go +andika(kweli || sikweli) // Tokeo: `kweli` + +andika(sikweli || sikweli) // Tokeo: `sikweli` +``` + +### Kitendakazi `!` + +Kitendakazi `!` hukanusha thamani ya kitu. Kwa mfano: + +```go +andika(!kweli) // Tokeo: `sikweli` + +andika(!sikweli) // Tokeo: `kweli` +``` + +## Kufanya Kazi na Thamani za Buliani Katika Vitanzi + +Katika Nuru, unaweza ukatumia semi za buliani katika vitanzi kuendesha tabia zake. Kwa mfano: + +```go +namba = [1, 2, 3, 4, 5] + +kwa thamani ktk namba { + kama (thamani % 2 == 0) { + andika(thamani, " ni namba shufwa") + } sivyo { + andika(thamani, " ni namba witiri") + } +} + +// Output: +// 1 ni namba witiri +// 2 ni namba shufwa +// 3 ni namba witiri +// 4 ni namba shufwa +// 5 ni namba witiri +``` + +Hapa , tumetengeneza safu yenye namba 1 hadi 5 kisha tukazunguka ndani ya safu hiyo na kwa kila namba tukatumia kitendakazi `%` ilikubaini kama namba ni shufwa au witiri. Matokeo yatakua ni "ni namba shufwa" kwa namba shufwa na "ni namba witiri" kwa namba witiri. + +Vitu buliani katika Nuru vinaweza kutumika kutathmini semi ambazo zinarudisha thamani ya kweli au sikweli. Unaweza kutumia vitendakazi vya buliani kutathmini semi tata na kuendesha tabia ya vitanzi. Kuelewa namna ya kufanya kazi na thamani za buliani ni ujuzi wamsingi kwa mtengenezaji programu yeyote wa Nuru. diff --git a/repl/docs/sw/builtins.md b/repl/docs/sw/builtins.md new file mode 100644 index 0000000..b4c188a --- /dev/null +++ b/repl/docs/sw/builtins.md @@ -0,0 +1,54 @@ +# Vitendakazi Vilivyojengwa Ndani ya Nuru + +Nuru ina vitendakazi kadhaa vilivyojengwa ndani vinavyofanya kazi husika. + +## Kitendakazi andika() + +Kitendakazi `andika()` kinatumika kuchapisha ujumbe kwenye konsoli. Inawezakuchukua hoja sifuri au zaidi, na hoja zitachapishwa na nafasi kati yao. Kwa kuongeza, `andika()` huhimili uundaji wa msingi kama vile `/n` kwa ajili ya mstari mpya, `/t` kwa ajili ya nafasi ya kichupo, na `\\` kwa ajili ya mkwajunyuma. Mfano: + +```go +andika(1, 2, 3) // Output: 1 2 3 +``` + +```go +andika("Jina: Asha /n Umri: 20 /n Chuo: IFM") + +// Output: +// Jina: Asha +// Umri: 20 +// Chuo: IFM +``` + +## Kitendakazi jaza() + +Kitendakazi `jaza()` kinatumika kupata ingizo kutoka kwa mtumiaji. Inawezakuchukua hoja sifuri au moja, ambayo ni utungo utakao tumika kama kimahasishi kwa mtumiaji. Mfano: + +```go +fanya salamu = unda() { + fanya jina = jaza("Unaitwa nani? ") + andika("Mambo vipi", jina) +} + +salamu() +``` + +Katika mfano huu, tunaainisha kitendakazi `salamu()` ambacho kinamhamasisha mtumiaji kuingiza jina kwa kutumia kitendakazi `jaza()`. Kisha tunatumia kitendakazi `andika()` kuchapisha ujumbe unaobeba jina la mtumiaji aliloingiza. + +## Kitendakazi aina() + +Kitendakazi `aina()` kinatumika kutambua aina ya kitu. Inakubali hoja moja, na thamani inayorudi hua ni utungo unaoonyesha aina ya kitu. Mfano: + +```go +aina(2) // Output: "NAMBA" +aina("Nuru") // Output: "NENO" +``` + +## Kitendakazi fungua() + +Kitendakazi `fungua()` kinatumika kufungua faili. Inakubali hoja moja, ambayo ni njia ya faili unalotaka kufungua. Mfano: + +```go +faili = fungua("data.txt") +``` + +Katika mfano huu, tumetumia kitendakazi `fungua()` kufungua faili linaloitwa "data.txt". Kibadilika `faili` kinabeba kumbukumbu ya faili lililofunguliwa. diff --git a/repl/docs/sw/dictionaries.md b/repl/docs/sw/dictionaries.md new file mode 100644 index 0000000..04f2762 --- /dev/null +++ b/repl/docs/sw/dictionaries.md @@ -0,0 +1,134 @@ +# Kamusi Katika Nuru + +Kamusi katika Nuru ni miundo ya data inayotunza jozi za funguo-thamani. Ukurasa huu unatoa maelezo kuhusu Kamusi katika Nuru, ikiwemo namna ya kutengeneza, namna ya kubadilisha, na namna ya kuzunguka ndani yake. + +## Kutengeneza Kamusi + +Kamusi zinawekwa kwenye mabano singasinga na hujumuisha funguo na thamani zake zikitenganishwa na nukta pacha. Mfano wa uainishwaji wa kamusi: + +```go + +orodha = {"jina": "Juma", "umri": 25} +``` + +Funguo zinawezakua tungo, namba, desimali, au buliani na thamani inaweza kua aina ya data yoyote ikiwemo tungo, namba, desimali, buliani, tupu, au kitendakazi: + +```go +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina thamani": tupu +} +``` + +## Kupata Vipengele + +Unaweza kupata vipengele vya kamusi kwa kutumia funguo zake: + +```go +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina thamani": tupu +} + +andika(k[kweli]) // kweli +andika(k["salimu"]("Juma")) // habari Juma +``` + +## Kuboresha Vipengele + +Boresha thamani ya kipengele kwa kukipa thamani mpya kwenye funguo yake: + +```go +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina thamani": tupu +} + +k['umri'] = 30 +andika(k['umri']) // 30 +``` + +## Kuongeza Vipengele Vipya + +Ongeza jozi mpya ya funguo-thamani kwenye kamusi kwa kuipa thamani funguo ambayo haipo kwenye kamusi husika: + +```go +k["lugha"] = "Kiswahili" +andika(k["lugha"]) // Kiswahili +``` + +## Kuunganisha Kamusi + +Unganisha kamusi mbili kwa kutumia kiendeshi `+`: + +```go +matunda = {"a": "apple", "b": "banana"} +mboga = {"c": "tembele", "d": "mchicha"} +vyakula = matunda + mboga +andika(vyakula) // {"a": "apple", "b": "banana", "c": "tembele", "d": "mchicha"} +``` + +## Angalia Kama Funguo Ipo Kwenye Kamusi + +Tumia neno msingi `ktk` kuangalia kama funguo ipo kwenye kamusi: + +```go + +k = { + "jina": "Juma", + "umri": 25, + kweli: "kweli", + "salimu": unda(x) { andika("habari", x) }, + "sina thamani": tupu +} + +"umri" ktk k // kweli +"urefu" ktk k // sikweli +``` + +## Kuzunguka Ndani Ya Kamusi + +Zunguka ndani ya kamusi kupata funguo na thamani zake: + +```go + +hobby = {"a": "kulala", "b": "kucheza mpira", "c": "kuimba"} + +kwa i, v ktk hobby { + andika(i, "=>", v) +} + +//Output + +a => kulala +b => kucheza mpira +c => kuimba +``` + +Kuzunguka ndani ya kamusi na kupata thamani peke yake: + +```go + +hobby = {"a": "kulala", "b": "kucheza mpira", "c": "kuimba"} + +kwa i, v ktk hobby { + andika(i, "=>", v) +} + +//Output + +kulala +kucheza mpira +kuimba +``` + +Kwa ufahamu huu, unaweza ukatumia kamusi kikamilifu katika Nuru kutunza na kusimamia jozi za funguo-thamani, na kupata namna nyumbufu ya kupangilia na kupata data katika programu zako. diff --git a/repl/docs/sw/for.md b/repl/docs/sw/for.md new file mode 100644 index 0000000..7883698 --- /dev/null +++ b/repl/docs/sw/for.md @@ -0,0 +1,166 @@ +# Vitanzi Vya Kwa Katika Nuru + +Vitanzi vya `kwa` ni muundo msingi wa udhibiti katika Nuru ambavyo hutumika kuzunguka vitu vinavyozungukika kama tungo, safu, na kamusi. Ukurasahuu unaangazia sintaksia na matumizi ya Vitanzi katika Nuru, ikiwemo kuzunguka ndani ya jozi ya funguo-thamani, na matumizi ya matamshi `vunja` na `endelea`. + +## Sintaksia + +Kutengeneza kitanzi cha `kwa`, tumia neno msingi `kwa` likifwatiwa na kitambulishi cha muda mfupi kama vile `i` au `v` na kitu kinachozungukika. Funga mwili wa kitanzi na mabano singasinga `{}`. Mfano unaotumia tungo: + +```go +jina = "lugano" + +kwa i ktk jina { + andika(i) +} + +// Tokeo: +l +u +g +a +n +o +``` + +## Kuzunguka Ndani ya Jozi ya Funguo-Thamani + +### Kamusi + +Nuru inakuruhusu kuzunguka ndani ya kamusi kupata thamani moja moja au jozi ya funguo na thamani yake. Kupata tu thamani, tumia kitambulisha cha muda mfupi: + +```go +kamusi = {"a": "andaa", "b": "baba"} + +kwa v ktk kamusi { + andika(v) +} + +// Tokeo: + +andaa +baba +``` + +Kupata thamani ya funguo na thamani zake, tumia vitambulishi vya muda mfupi viwili: + +```go + +kwa k, v ktk kamusi { + andika(k + " ni " + v) +} + +// Tokeo: + +a ni andaa +b ni baba +``` + +### Tungo + +Kuzunguka juu ya thamani za tungo, tumia kitambulishi cha muda mfupi: + +```go +kwa v ktk "mojo" { + andika(v) +} + +// Tokeo: + +m +o +j +o +``` + +Kuzunguka juu ya funguo na thamani zake, tumia vitambulishi vya muda mfupi viwili: + +```go +kwa i, v ktk "mojo" { + andika(i, "->", v) +} + +// Tokeo: + +0 -> m +1 -> o +2 -> j +3 -> o +``` + +### Safu + +Kuzunguka juu ya thamani za safu, tumia kitambulishi cha muda mfupi: + +```go +majina = ["juma", "asha", "haruna"] + +kwa v ktk majina { + andika(v) +} + +// Tokeo: + +juma +asha +haruna +``` + +Kuzunguka juu ya funguo na thamani katika safy, tumia vitambulishi vya muda mfupi viwili: + +```go +kwa i, v ktk majina { + andika(i, "-", v) +} + +// Tokeo: + +0 - juma +1 - asha +2 - haruna +``` + +## Vunja na Endelea + +### Vunja + +Tumia neno msingi `vunja` kisitisha kitanzi: + +```go + +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimevunja") + vunja + } + andika(v) +} + +// Tokeo: + +m +o +j +nimevunja + +``` + +### Endelea + +Tumia neno msingi `endelea` kuruka mzunguko maalum: + +```go +kwa i, v ktk "mojo" { + kama (i == 2) { + andika("nimeruka") + endelea + } + andika(v) +} + +// Tokeo: + +m +o +nimeruka +o +``` diff --git a/repl/docs/sw/functions.md b/repl/docs/sw/functions.md new file mode 100644 index 0000000..d913a52 --- /dev/null +++ b/repl/docs/sw/functions.md @@ -0,0 +1,91 @@ +# Undo (Functions) + +Vitendakazi ni sehemu ya msingi ya Nuru inayokuwezesha kuainisha mapande ya msimbo yanayoweza kutumika tena. Ukurasa huu unaainisha sintaksia na matumizi ya vitendakazi katika nuru ikiwemo vipengele, vipengele vya msingi, matamshi ya kurudisha, kujirudia, na vifungizi. + +## Sintaksia + +Pande la kitendakazi huanza na neno msingi `unda` likifuatiwa na vipengele vinavyowekwa ndani ya mabano `()` na mwili unaowekwa ndani ya mabano singasinga `{}`. Vitendakazi lazima viwekwe kwenye kibadiliki: + +```go +jumla = unda(x, y) { + rudisha x + y +} + +jumla(2, 3) // 5 +``` + +## Vipengele + +Vitendakazi vinawezakuwa nazifuri au idadi yoyote ya vipengele. Vipengele vinawezakua vya aina yoyote hata vitendakazi vingine: + +```go +salamu = unda(jina) { + andika("Habari yako", jina) +} + +salamu("asha") // Habari yako asha +``` + +## Vipengele Vya Msingi + +Vitendakazi vinawezakupewa vipengele vya msingi: + +```go +salimu = unda(salamu="Habari") { + andika(salamu) +} + +salimu() // Habari +salimu("Mambo") // Mambo +``` + +## Rudisha + +Unaweza pia ukarudisha thamani kwa kutumia neno msingi `rudisha`. Neno msingi `rudisha` husitisha pande la msimbo na kurudisha thamani: + +```go +mfano = unda(x) { + rudisha "nimerudi" + andika(x) +} + +mfano("x") // nimerudi +``` + +## Kujirudia + +Nuru pia inahimili kujirudia. Mfano wa kujirudia kwa kitendakazi cha Fibonacci: + +```go + +fib = unda(n) { + kama (n <= 1) { + rudisha n + } sivyo { + rudisha fib(n-1) + fib(n-2) + } +} + +andika(fib(10)) // 55 +``` + +Kitendakazi fib kinakokotoa namba ya Fibonacci ya n kwa kujiita yenyewe ikiwa na n-1 na n-2 kama vipengele mpaka ambapo n ni ndogo kuliko au sawa na moja. + +## Vifungizi + +Vifungizi ni vitendakazi visivyo na jina ambayo vinaweza kudaka na kuhifadhi marejeo ya vibadilika kutoka katika muktadha unaovizunguka. Katika Nuru, unaweza kutengeneza vifungizi kwa kutumia neno msingin `unda` bila kuiweka kwenye kibadiliki. Mfano: + +```go +fanya jum = unda(x) { + rudisha unda(y) { + rudisha x + y + } +} + +fanya jum_x = jum(5) +andika(jum_x(3)) // 8 +``` + +Katika mfano hapo juu, kitendakazi `jum` kinarudisha kitendakazi kingine ambacho kinabeba kipengele kimoja tu `y`. Kitendakazi kinachorudisha kinawezakupata kibadiliki x kutoka katika muktadha unaokizunguka. + +Sasa umeshaelewa misingi ya vitendakazi katika Nuru, ikiwemo kujirudia na vifungizi, unaweza ukatengeneza mapande ya msimbo yanayoweza kutumika tena na tena na kurahisisha programu zako na kuboresha mpangilio wa msimbo wako. diff --git a/repl/docs/sw/identifiers.md b/repl/docs/sw/identifiers.md new file mode 100644 index 0000000..6dd592b --- /dev/null +++ b/repl/docs/sw/identifiers.md @@ -0,0 +1,32 @@ +# Vitambulisho katika Nuru + +Vitambulisho hutumika kuweka majina kwenye vigezo, vitendakazi na vipengele vingine katika msimbo wako wa Nuru. Ukurasa huu unashughulikia sheria na mbinu bora za kuunda vitambulisho katika Nuru. + +## Sheria za Sintaksia + +Vitambulisho vinaweza kuwa na herufi, nambari na nistari wa chini `_`. Walakini, kuna sheria chache ambazo unapaswa kufuata wakati wa kuunda vitambulisho: + +- Vitambulisho haviwezi kuanza na nambari. +- Vitambulisho huwa na tofauti kulingana na matumizi ya herufi kubwa na ndogo. Kwa mfano, `kibadilikaChangu` na `kibadilikachangu` huchukuliwa kuwa vitambulisho tofauti. + +Hapa kuna mifano ya vitambulisho halali: + +```go +fanya mwaka_wa_kuzaliwa = 2020 +andika(mwaka_wa_kuzaliwa) // 2020 + +fanya badili_c_kwenda_p = "C kwenda P" +andika(badili_c_kwenda_p) // "C kwenda P" +``` + +Katika mifano iliyo hapo juu, mwaka_wa_kuzaliwa na badili_c_kwenda_p zote ni vitambulisho halali. + +## Mazoea Bora + +Wakati wa kuchagua vitambulisho, ni muhimu kufuata mazoea bora ili kuhakikisha kuwa msimbo wako uko wazi na rahisi kueleweka: + +- Tumia majina yanayoelezea wazi kusudi au maana ya kigezo au kitendakazi. +- Fuata kanuni thabiti ya kuweka majina, kama vile camelCase (kibadilikaChangu) au snake_case (kibadilika_changu). +- Epuka kutumia majina tofauti ya herufi moja, isipokuwa kwa vijisehemu vinavyokubalika kwa kawaida kama vile vihesabu vitanzi (i, j, k). + +Kwa kufuata mbinu bora hizi unapounda vitambulisho, utafanya code yako ya Nuru iwe rahisi kusoma na kutunza kwa wewe na wengine. diff --git a/repl/docs/sw/if.md b/repl/docs/sw/if.md new file mode 100644 index 0000000..a364c61 --- /dev/null +++ b/repl/docs/sw/if.md @@ -0,0 +1 @@ +# Kama/Sivyo (If/Else) \ No newline at end of file diff --git a/repl/docs/sw/keywords.md b/repl/docs/sw/keywords.md new file mode 100644 index 0000000..5160a95 --- /dev/null +++ b/repl/docs/sw/keywords.md @@ -0,0 +1 @@ +# Maneno Muhimu (Keywords) \ No newline at end of file diff --git a/repl/docs/sw/maoni.md b/repl/docs/sw/maoni.md new file mode 100644 index 0000000..a0ca683 --- /dev/null +++ b/repl/docs/sw/maoni.md @@ -0,0 +1,29 @@ +# Maoni Katika Nuru + +Katika Nuru, unaweza kuandika maoni kutoa maelezo na hati kwa kazi yako. Maoni ni mistari ya maandishi ambayo hupuuzwa na mfasiri (interpreter) wa Nuru, kwa hivyo haitaathiri tabia ya programu yako. Kuna aina mbili za maoni katika Nuru: maoni ya mstari mmoja na maoni ya mistari mingi. + +## Maoni ya Mstari Mmoja + +Maoni ya mstari mmoja yanatumiwa kutoa maelezo mafupi au hati kwa mstari mmoja wa kazi. Kuandika maoni ya mstari mmoja katika Nuru, tumia mikwaju miwili ya mbele (//) ikifuatiwa na maandishi ya maoni yako. Hapa kuna mfano: + +```s +// Mstari huu utapuuzwa na mfasiri wa Nuru +``` + +Katika mfano huu, maandishi ya maoni "Mstari huu utapuuzwa na mfasiri wa Nuru" yatapuuzwa na mfasiri, kwa hivyo haitaathiri tabia ya programu. + +## Maoni ya Mistari Mingi + +Maoni ya mistari mingi yanatumiwa kutoa maelezo ya kina zaidi au hati kwa mistari mingi ya programu yako. Kuandika maoni ya mistari mingi katika Nuru, tumia ukwaju wa mbele ikifuatiwa na nyota ( /* ) kuanza maoni, na nyota ikifuatiwa na ukwaju wa mbele ( */ ) kumaliza maoni. Hapa kuna mfano: + +```s +/ +Mistari hii +Ita +puuzwa +/ +``` + +Katika mfano huu, mistari yote kati ya alama /* na */ itapuuzwa na mfasiri wa Nuru, kwa hivyo haitaathiri tabia ya programu. + +Kwa kutumia maoni ya mstari mmoja na maoni ya mistari mingi katika Nuru, unaweza kufanya kazi yako iwe rahisi kusoma na kudumisha kwa ajili yako na wengine ambao watahitaji kufanya kazi na programu yako katika siku zijazo. diff --git a/repl/docs/sw/null.md b/repl/docs/sw/null.md new file mode 100644 index 0000000..628c65e --- /dev/null +++ b/repl/docs/sw/null.md @@ -0,0 +1 @@ +# Tupu (Null) \ No newline at end of file diff --git a/repl/docs/sw/numbers.md b/repl/docs/sw/numbers.md new file mode 100644 index 0000000..dd4ec8b --- /dev/null +++ b/repl/docs/sw/numbers.md @@ -0,0 +1 @@ +# Namba na Desimali (Ints/Floats) \ No newline at end of file diff --git a/repl/docs/sw/operators.md b/repl/docs/sw/operators.md new file mode 100644 index 0000000..cf38456 --- /dev/null +++ b/repl/docs/sw/operators.md @@ -0,0 +1 @@ +# Matendaji (Operators) \ No newline at end of file diff --git a/repl/docs/sw/range.md b/repl/docs/sw/range.md new file mode 100644 index 0000000..d8e73c3 --- /dev/null +++ b/repl/docs/sw/range.md @@ -0,0 +1,55 @@ +## Kitendakazi cha Mfululizo + +Kitendakazi cha `mfululizo` hutoa mfululizo wa nambari, sawa na kitendakazi cha `range()` cha Python. Kinaweza kutumika katika vitanzi au kuunda safu za nambari zinazofuatana. + +### Muundo + +```go +mfululizo(mwisho) +mfululizo(mwanzo, mwisho) +mfululizo(mwanzo, mwisho, hatua) +``` + +### Vipengele + +- `mwisho`: Kikomo cha juu cha mfululizo (haijumuishwi). +- `mwanzo` (si lazima): Thamani ya kuanzia ya mfululizo. Chaguo-msingi ni 0. +- `hatua` (si lazima): Ongezeko kati ya kila nambari katika mfululizo. Chaguo-msingi ni 1. + +### Thamani Inayorudishwa + +Hurudisha safu ya nambari kamili. + +### Mifano + +```go +// Toa nambari kutoka 0 hadi 4 +kwa i katika mfululizo(5) { + andika(i) +} +// Tokeo: 0 1 2 3 4 + +// Toa nambari kutoka 1 hadi 9 +kwa i katika mfululizo(1, 10) { + andika(i) +} +// Tokeo: 1 2 3 4 5 6 7 8 9 + +// Toa nambari shufwa kutoka 0 hadi 8 +kwa i katika mfululizo(0, 10, 2) { + andika(i) +} +// Tokeo: 0 2 4 6 8 + +// Toa nambari kwa mpangilio wa kurudi nyuma +kwa i katika mfululizo(10, 0, -1) { + andika(i) +} +// Tokeo: 10 9 8 7 6 5 4 3 2 1 +``` + +### Vidokezo + +- Thamani ya `mwisho` haijumuishwi, ikimaanisha mfululizo utasimama kabla ya kufikia thamani hii. +- Ikiwa `hatua` hasi imetolewa, `mwanzo` inapaswa kuwa kubwa kuliko `mwisho`. +- Thamani ya `hatua` haiwezi kuwa sifuri. diff --git a/repl/docs/sw/strings.md b/repl/docs/sw/strings.md new file mode 100644 index 0000000..0185efb --- /dev/null +++ b/repl/docs/sw/strings.md @@ -0,0 +1 @@ +# Neno (Strings) \ No newline at end of file diff --git a/repl/docs/sw/switch.md b/repl/docs/sw/switch.md new file mode 100644 index 0000000..ec1fc19 --- /dev/null +++ b/repl/docs/sw/switch.md @@ -0,0 +1 @@ +# Badili (Switch) \ No newline at end of file diff --git a/repl/docs/sw/while.md b/repl/docs/sw/while.md new file mode 100644 index 0000000..e022883 --- /dev/null +++ b/repl/docs/sw/while.md @@ -0,0 +1 @@ +# Wakati (While) \ No newline at end of file diff --git a/repl/repl.go b/repl/repl.go index bd99864..e9c73c0 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -1,44 +1,28 @@ package repl import ( - "bufio" + "embed" "fmt" - "io" + "log" "os" "strings" - "github.com/AvicennaJr/Nuru/evaluator" - "github.com/AvicennaJr/Nuru/lexer" - "github.com/AvicennaJr/Nuru/object" - "github.com/AvicennaJr/Nuru/parser" + prompt "github.com/AvicennaJr/GoPrompt" + "github.com/NuruProgramming/Nuru/evaluator" + "github.com/NuruProgramming/Nuru/lexer" + "github.com/NuruProgramming/Nuru/object" + "github.com/NuruProgramming/Nuru/parser" + "github.com/NuruProgramming/Nuru/styles" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + zone "github.com/lrstanley/bubblezone" ) const PROMPT = ">>> " -const ERROR_FACE = ` - ███████████████████████████ - ███████▀▀▀░░░░░░░▀▀▀███████ - ████▀░░░░░░░░░░░░░░░░░▀████ - ███│░░░░░░░░░░░░░░░░░░░│███ - ██▌│░░░░░░░░░░░░░░░░░░░│▐██ - ██░└┐░░░░░░░░░░░░░░░░░┌┘░██ - ██░░└┐░░░░░░░░░░░░░░░┌┘░░██ - ██░░┌┘▄▄▄▄▄░░░░░▄▄▄▄▄└┐░░██ - ██▌░│██████▌░░░▐██████│░▐██ - ███░│▐███▀▀░░▄░░▀▀███▌│░███ - ██▀─┘░░░░░░░▐█▌░░░░░░░└─▀██ - ██▄░░░▄▄▄▓░░▀█▀░░▓▄▄▄░░░▄██ - ████▄─┘██▌░░░░░░░▐██└─▄████ - █████░░▐█─┬┬┬┬┬┬┬─█▌░░█████ - ████▌░░░▀┬┼┼┼┼┼┼┼┬▀░░░▐████ - █████▄░░░└┴┴┴┴┴┴┴┘░░░▄█████ - ███████▄░░░░░░░░░░░▄███████ - ██████████▄▄▄▄▄▄▄██████████ - ███████████████████████████ - - █▄▀ █░█ █▄░█ ▄▀█   █▀ █░█ █ █▀▄ ▄▀█ - █░█ █▄█ █░▀█ █▀█   ▄█ █▀█ █ █▄▀ █▀█ - -` + +//go:embed docs +var res embed.FS func Read(contents string) { env := object.NewEnvironment() @@ -49,64 +33,138 @@ func Read(contents string) { program := p.ParseProgram() if len(p.Errors()) != 0 { - fmt.Println(colorfy(ERROR_FACE, 31)) - fmt.Println("Kuna Errors Zifuatazo:") + fmt.Println(styles.ErrorStyle.Italic(false).Render("Kuna Errors Zifuatazo:")) for _, msg := range p.Errors() { - fmt.Println("\t" + colorfy(msg, 31)) + fmt.Println("\t" + styles.ErrorStyle.Render(msg)) } } evaluated := evaluator.Eval(program, env) if evaluated != nil { - fmt.Println(colorfy(evaluated.Inspect(), 32)) + if evaluated.Type() != object.NULL_OBJ { + fmt.Println(styles.ReplStyle.Render(evaluated.Inspect())) + } } } -func Start(in io.Reader, out io.Writer) { - - scanner := bufio.NewScanner(in) +func Start() { env := object.NewEnvironment() - for { - fmt.Print(PROMPT) - scanned := scanner.Scan() - if !scanned { - return - } + var d dummy + d.env = env + p := prompt.New( + d.executor, + completer, + prompt.OptionPrefix(PROMPT), + prompt.OptionTitle("Nuru Programming Language"), + ) - line := scanner.Text() - if strings.TrimSpace(line) == "exit()" || strings.TrimSpace(line) == "toka()" { - fmt.Println("✨🅺🅰🆁🅸🅱🆄 🆃🅴🅽🅰✨") - os.Exit(0) - } - l := lexer.New(line) - p := parser.New(l) + p.Run() +} - program := p.ParseProgram() +type dummy struct { + env *object.Environment +} - if len(p.Errors()) != 0 { - printParseErrors(out, p.Errors()) - continue +func (d *dummy) executor(in string) { + if strings.TrimSpace(in) == "exit()" || strings.TrimSpace(in) == "toka()" { + fmt.Println(lipgloss.NewStyle().Render("\n🔥🅺🅰🆁🅸🅱🆄 🆃🅴🅽🅰 🔥")) + os.Exit(0) + } + l := lexer.New(in) + p := parser.New(l) + + program := p.ParseProgram() + + if len(p.Errors()) != 0 { + for _, msg := range p.Errors() { + fmt.Println("\t" + styles.ErrorStyle.Render(msg)) } - evaluated := evaluator.Eval(program, env) - if evaluated != nil { - io.WriteString(out, colorfy(evaluated.Inspect(), 32)) - io.WriteString(out, "\n") + } + env := d.env + evaluated := evaluator.Eval(program, env) + if evaluated != nil { + if evaluated.Type() != object.NULL_OBJ { + fmt.Println(styles.ReplStyle.Render(evaluated.Inspect())) } } + } -func printParseErrors(out io.Writer, errors []string) { - //io.WriteString(out, colorfy(ERROR_FACE, 31)) - io.WriteString(out, "Kuna Errors Zifuatazo:\n") +func completer(in prompt.Document) []prompt.Suggest { + return []prompt.Suggest{} +} - for _, msg := range errors { - io.WriteString(out, "\t"+colorfy(msg, 31)+"\n") +func Docs() { + zone.NewGlobal() + + languageChoice := []list.Item{ + languages{title: "Kiswahili", desc: "Soma nyaraka kwa Kiswahili", dir: "sw"}, + languages{title: "English", desc: "Read documentation in English", dir: "en"}, } -} -func colorfy(str string, colorCode int) string { - return fmt.Sprintf("\x1b[%dm%s\x1b[0m", colorCode, str) + var p playground + + p.languageCursor = list.New(languageChoice, list.NewDefaultDelegate(), 50, 8) + p.languageCursor.Title = "Chagua Lugha" + p.languageCursor.SetFilteringEnabled(false) + p.languageCursor.SetShowStatusBar(false) + p.languageCursor.SetShowPagination(false) + p.languageCursor.SetShowHelp(false) + p.toc = list.New(englishItems, list.NewDefaultDelegate(), 0, 0) + p.toc.Title = "Table of Contents" + p.id = zone.NewPrefix() + + if _, err := tea.NewProgram(p, tea.WithMouseAllMotion()).Run(); err != nil { + log.Fatal(err) + } } + +var ( + englishItems = []list.Item{ + item{title: "Arrays", desc: "🚀 Unleash the power of arrays in Nuru", filename: "arrays.md"}, + item{title: "Booleans", desc: "👍👎 Master the world of 'if' and 'else' with bools", filename: "bool.md"}, + item{title: "Builtins", desc: "💡 Reveal the secrets of builtin functions in Nuru", filename: "builtins.md"}, + item{title: "Comments", desc: "💬 Speak your mind with comments in Nuru", filename: "comments.md"}, + item{title: "Dictionaries", desc: "📚 Unlock the knowledge of dictionaries in Nuru", filename: "dictionaries.md"}, + item{title: "Files", desc: "💾 Handle files effortlessly in Nuru", filename: "files.md"}, + item{title: "For", desc: "🔄 Loop like a pro with 'for' in Nuru", filename: "for.md"}, + item{title: "Function", desc: "🔧 Create powerful functions in Nuru", filename: "function.md"}, + item{title: "Identifiers", desc: "🔖 Give your variables their own identity in Nuru", filename: "identifiers.md"}, + item{title: "If Statements", desc: "🔮 Control the flow with 'if' statements in Nuru", filename: "ifStatements.md"}, + item{title: "JSON", desc: "📄 Master the art of JSON in Nuru", filename: "json.md"}, + item{title: "Keywords", desc: "🔑 Learn the secret language of Nuru's keywords", filename: "keywords.md"}, + item{title: "Net", desc: "🌐 Explore the world of networking in Nuru", filename: "net.md"}, + item{title: "Null", desc: "🌌 Embrace the void with Null in Nuru", filename: "null.md"}, + item{title: "Numbers", desc: "🔢 Discover the magic of numbers in Nuru", filename: "numbers.md"}, + item{title: "Operators", desc: "🧙 Perform spells with Nuru's operators", filename: "operators.md"}, + item{title: "Packages", desc: "📦 Harness the power of packages in Nuru", filename: "packages.md"}, + item{title: "Strings", desc: "🎼 Compose stories with strings in Nuru", filename: "strings.md"}, + item{title: "Switch", desc: "🧭 Navigate complex scenarios with 'switch' in Nuru", filename: "switch.md"}, + item{title: "Time", desc: "⏰ Manage time with ease in Nuru", filename: "time.md"}, + item{title: "While", desc: "⌛ Learn the art of patience with 'while' loops in Nuru", filename: "while.md"}, + } + + kiswahiliItems = []list.Item{ + item{title: "Maoni Katika Nuru", desc: "💬 Toa mawazo yako na maoni (comments) katika Nuru", filename: "maoni.md"}, + item{title: "Vitambulishi", desc: "🔖 Toa utambulisho wa kipekee kwa vigezo vyako katika Nuru", filename: "identifiers.md"}, + item{title: "Nambari", desc: "🔢 Gundua uchawi wa nambari katika Nuru", filename: "numbers.md"}, + item{title: "Maneno", desc: "🎼 Tunga hadithi kwa kutumia maneno katika Nuru", filename: "strings.md"}, + item{title: "Kamusi", desc: "📚 Fungua maarifa ya kamusi katika Nuru", filename: "dictionaries.md"}, + item{title: "Buliani", desc: "👍👎 Kuwa mtaalam wa ulimwengu wa 'if' na 'else' kwa kutumia bool", filename: "bools.md"}, + item{title: "Tupu", desc: "🌌 Kubali utupu na Null katika Nuru", filename: "null.md"}, + item{title: "Safu", desc: "🚀 Fungua nguvu za safu (arrays) katika Nuru", filename: "arrays.md"}, + item{title: "Kwa", desc: "🔄 Rudia kama mtaalam kwa kutumia 'kwa' katika Nuru", filename: "for.md"}, + item{title: "Wakati", desc: "⌛ Jifunze sanaa ya subira na vitanzi vya 'wakati' katika Nuru", filename: "while.md"}, + item{title: "Undo", desc: "🔧 Unda kazi zenye nguvu katika Nuru", filename: "function.md"}, + item{title: "Badili", desc: "🧭 Elekeza hali ngumu kwa kutumia 'badili' katika Nuru", filename: "switch.md"}, + item{title: "Faili", desc: "💾 Shughulikia faili kwa urahisi katika Nuru", filename: "files.md"}, + item{title: "Muda", desc: "⏰ Simamia muda kwa urahisi katika Nuru", filename: "time.md"}, + item{title: "JSON", desc: "📄 Kuwa mtaalam wa sanaa ya JSON katika Nuru", filename: "json.md"}, + item{title: "Mtandao", desc: "🌐 Chunguza ulimwengu wa mitandao katika Nuru", filename: "net.md"}, + item{title: "Vifurushi", desc: "📦 Tumia nguvu za vifurushi katika Nuru", filename: "packages.md"}, + item{title: "Vijenzi", desc: "💡 Funua siri za kazi za kujengwa katika Nuru", filename: "builtins.md"}, + } +) diff --git a/sh/install.sh b/sh/install.sh new file mode 100755 index 0000000..cd29522 --- /dev/null +++ b/sh/install.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env sh + +# Hii ni skripti ya shell ili kusakinisha programu ya Nuru. +# Programu zinazohitajika: +# - curl/wget: Kupakua faili za 'tar' kutoka 'Github' +# - cp: Nakili faili kuenda mahali sahihi +# - jq: Kupata uhusiano kwenye fomati ya 'JSON' +# - tar: Kufungua faili za tar.gz + +set -e + +ARCH="$(uname -m)" +OSNAME="$(uname -s)" +PREFIX_PATH="/usr" +BIN="" +VERSION="latest" +RELEASE_URL="https://github.com/NuruProgramming/Nuru/releases" +TEMP="" + +# Cleanup function to remove temp directory on exit +cleanup() { + if [ -n "$TEMP" ] && [ -d "$TEMP" ]; then + rm -rf "$TEMP" + fi +} +trap cleanup EXIT + +# Check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Print usage information +usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -p, --prefix The base path to be used when installing (default: /usr)" + echo " -v, --version The version to be downloaded from GitHub (default: latest)" + echo " -h, --help Show this help message" + echo "" +} + +# Normalize architecture names +arch_name() { + case "$ARCH" in + x86_64) + ARCH="amd64" + ;; + i386|i686) + ARCH="i386" + ;; + arm64|aarch64) + ARCH="arm64" + ;; + *) + echo "Unsupported architecture: $ARCH" + exit 2 + ;; + esac +} + +# Validate OS name +os_name() { + case "$OSNAME" in + Darwin|Linux|Android) + ;; + *) + echo "Unsupported Operating System: $OSNAME" + exit 2 + ;; + esac +} + +# Parse command line arguments +parse_args() { + while [ "$#" -gt 0 ]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -p|--prefix) + shift + if [ -z "$1" ]; then + echo "Error: Missing argument for --prefix" + exit 1 + fi + PREFIX_PATH="$1" + ;; + -v|--version) + shift + if [ -z "$1" ]; then + echo "Error: Missing argument for --version" + exit 1 + fi + VERSION="$1" + ;; + --) + shift + break + ;; + *) + echo "Unknown argument: $1" + usage + exit 1 + ;; + esac + shift + done + BIN="$PREFIX_PATH/bin" +} + +# Download file using curl or wget +download() { + URL="$1" + if command_exists curl; then + curl -fSL "$URL" + elif command_exists wget; then + wget -qO- "$URL" + else + echo "Error: Neither curl nor wget is installed." + exit 1 + fi +} + +main() { + os_name + arch_name + parse_args "$@" + + # Check required commands + for cmd in jq tar cp; do + if ! command_exists "$cmd"; then + echo "Error: Required command '$cmd' not found." + exit 1 + fi + done + + if [ "$VERSION" = "latest" ]; then + echo "Fetching latest version tag from GitHub..." + VERSION="$(download "https://api.github.com/repos/NuruProgramming/Nuru/releases/latest" | jq -r .tag_name)" + if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then + echo "Error: Unable to determine latest version." + exit 1 + fi + fi + + TAR_URL="$RELEASE_URL/download/$VERSION/nuru_${OSNAME}_${ARCH}.tar.gz" + echo "Downloading Nuru version $VERSION for $OSNAME/$ARCH..." + + TEMP="$(mktemp -d)" + if ! download "$TAR_URL" | tar -xz -C "$TEMP"; then + echo "Error: Failed to download or extract archive." + exit 1 + fi + + # Ensure bin directory exists + if [ ! -d "$BIN" ]; then + echo "Creating directory $BIN" + mkdir -p "$BIN" + fi + + echo "Installing Nuru to $BIN/nuru" + if ! cp "$TEMP/nuru" "$BIN/"; then + echo "Error: Failed to copy binary to $BIN" + exit 1 + fi + + echo "Installation complete." +} + +main "$@" diff --git a/styles/styles.go b/styles/styles.go new file mode 100644 index 0000000..4dc1383 --- /dev/null +++ b/styles/styles.go @@ -0,0 +1,13 @@ +package styles + +import "github.com/charmbracelet/lipgloss" + +var ( + TitleStyle = lipgloss.NewStyle().Margin(1, 0).Foreground(lipgloss.Color("#aa6f5a")) + VersionStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff9671")) + AuthorStyle = lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("#ff9671")) + HelpStyle = lipgloss.NewStyle().Italic(true).Faint(true).Foreground(lipgloss.Color("#ffe6d6")) + ErrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196")).Italic(true) + ReplStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("76")).Italic(true) + PromptStyle = "" +) diff --git a/third_party/math/README.md b/third_party/math/README.md new file mode 100644 index 0000000..08a2e38 --- /dev/null +++ b/third_party/math/README.md @@ -0,0 +1,253 @@ +# Pakeji Hesabu (Math Package) + +Pakeji Hesabu is a math package written in pure Nuru by [VictorKariuki](https://github.com/VictorKariuki). + +This package provides various mathematical functions and constants implemented in nuru programming language. It includes methods for `trigonometric functions`, `logarithmic functions`, `array operations`, and `utility functions`. + + +## Usage +To use the `pakeji hesabu` package follow the steps below: + +1. Copy the `hesabu.nr` file and any required third-party package files into the same directory as your project. + +2. Ensure that the package file names end with the `.nr` extension and match the package names. For example, if the package name is `hesabu`, the corresponding file name should be `hesabu.nr`. + +3. You can directly import the `hesabu.nr` package and any required third-party packages in your Nuru code using the `tumia` keyword. For example: + + ```nuru + tumia "hesabu" + ``` + Example of calling the package methods: + ```nuru + andika(hesabu.e()) +## What is in +This package covers a wide range of mathematical operations, including `basic arithmetic`, `trigonometry`, `exponential and logarithmic functions`, `rounding and comparison operations`, as well as some `utility and array operations`. + +The methods provided in the `hesabu` package can be classified into different categories based on their functionalities. Here is a classification of the methods: + +1. Trigonometric Functions: + - `cos(x)` + - `sin(x)` + - `tan(x)` + - `acos(x)` + - `asin(x)` + - `atan(x)` + +2. Hyperbolic Functions: + - `cosh(x)` + - `sinh(x)` + - `tanh(x)` + - `acosh(x)` + - `asinh(x)` + - `atanh(x)` + +3. Exponential and Logarithmic Functions: + - `exp(x)` + - `expm1(x)` + - `log(x)` + - `log10(x)` + - `log1p(x)` + +4. Other Mathematical Functions: + - `abs(namba)` + - `ceil(x)` + - `floor(x)` + - `sqrt(x)` + - `cbrt(x)` + - `root(x, n)` + - `hypot(values)` + - `factorial(n)` + +5. Rounding and Comparison Functions: + - `round(x, method)` + - `max(numbers)` + - `min(numbers)` + +6. Utility Functions: + - `sign(x)` + - `isNegative(num)` + - `isInteger(num)` + - `getIntegerPart(num)` + +7. Array and List Operations: + - `list(first, last, interval)` + - `reduce(iterator, callback, initialValue)` + + +### 1. Constants: + - **PI**: Represents the mathematical constant `π`. + - **e**: Represents `Euler's Number`. + - **phi**: Represents the `Golden Ratio`. + - **ln10**: Represents the `natural logarithm of 10`. + - **ln2**: Represents the `natural logarithm of 2`. + - **log10e**: Represents the `base 10 logarithms` of Euler's number `(e)`. + - **log2e**: Represents the `base 2 logarithm` of Euler's number` (e)`. + - **sqrt1_2**: Represents the `square root` of `1/2`. + - **sqrt2**: Represents the `square root` of `2`. + - **sqrt3**: Represents the `square root` of `3`. + - **sqrt5**: Represents the `square root` of `5`. + - **EPSILON**: Represents a small value (2.220446049250313e-16). + +### 2. Methods: + +1. **abs(namba)** + - Description: Calculates the absolute value of a number. + - Example: `hesabu.abs(-42)` returns `42`. + +2. **acos(x)** + - Description: Calculates the arccosine of a number. + - Example: `hesabu.acos(0.5)` returns `1.0471975511965979`. + +3. **acosh(x)** + - Description: Calculates the inverse hyperbolic cosine of a number. + - Example: `hesabu.acosh(2)` returns `1.3169578969248166`. + +4. **asin(x)** + - Description: Calculates the arcsine of a number using the Taylor series. + - Example: `hesabu.arcsin(0.5)` returns `0.5235987755982989`. + +5. **asinh(x)** + - Description: Calculates the inverse hyperbolic sine of a number. + - Example: `hesabu.arsinh(2)` returns `1.4436354751788103`. + +6. **atan(x)** + - Description: Calculates the arctangent of a number using the Taylor series. + - Example: `hesabu.atan(1)` returns `0.7853981633974485`. + +7. **atan2(y, x)** + - Description: Calculates the arctangent of the quotient of its arguments. + - Example: `hesabu.atan2(1, 1)` returns `0.7853981633974483`. + +8. **atanh(x)** + - Description: Calculates the inverse hyperbolic tangent of a number. + - Example: `hesabu.atanh(0.5)` returns `0.5493061443340549`. + +9. **cbrt(x)** + - Description: Calculates the cube root of a number. + - Example: `hesabu.cbrt(8)` returns `2`. + +10. **root(x, n)** + - Description: Calculates the nth root of a number using the Newton-Raphson method. + - Example: `hesabu.root(27, 3)` returns `3`. + +11. **ceil(x)** + - Description: Rounds up to the smallest integer greater than or equal to a given number. + - Example: `hesabu.ceil(4.3)` returns `5`. + +12. **cos(x)** + - Description: Calculates the cosine of an angle in radians using the Taylor series. + - Example: `hesabu.cos(5)` returns `0.28366218546322464`. + +13. **cosh(x)** + - Description: Calculates the hyperbolic cosine of a number. + - Example: `hesabu.cosh(5)` returns `74.20994842490012`. + +14. **exp(x)** + - Description: Calculates the value of Euler's number raised to the power of a given number. + - Example: `hesabu.exp(2)` returns `7.389056098930649`. + +15. **expm1(x)** + - Description: Calculates Euler's number raised to the power of a number minus 1. + - Example: `hesabu.expm1(1)` returns `1.7182818284590455`. + +16. **floor(x)** + - Description: Rounds down to the largest integer less than or equal to a given number. + - Example: `hesabu.floor(4.7)` returns `4`. + +17. **hypot(values)** + - Description: Calculates the square root of the sum of squares of the given values. + - Example: `hesabu.hypot([3, 4])` returns `5`. + +18. **log(x)** + - Description: Calculates the natural logarithm of a number. + - Example: `hesabu.log(2)` returns `0.69314718056`. + +19. **log10(x)** + - Description: Calculates the base 10 logarithm of a number. + - Example: `hesabu.log10(100)` returns `1.9999999999573126`. + +20. **log1p(x)** + - Description: Calculates the natural logarithm of 1 plus the given number. + - Example: `hesabu.log1p(1)` returns `0.6931471805599451`. + +21. **log2(x)** + - Description: Calculates the base 2 logarithm of a number. + - Example: `hesabu.log2(8)` returns `3`. + +22. **max(numbers)** + - Description: Finds the maximum value in a list of numbers. + - Example: `hesabu.max([4, 2, 9, 5])` returns `9`. + +23. **min(numbers)** + - Description: Finds the minimum value in a list of numbers. + - Example: `hesabu.min([4, 2, 9, 5])` returns `2`. + +24. **round(x, method)** + - Description: Rounds a number to the nearest integer using the specified method. + - supported methods: + - "rpi" (round to the nearest integer using the principle of rounding half to the nearest even) + - "rni" (round to the nearest integer using the principle of rounding half away from zero) + - "ri" (round to the nearest integer using the standard rounding method) + - An invalid method results in returning NaN (Not a Number) + - Example: `hesabu.round(4.6, "rpi")` returns `5`. + +25. **sign(x)** + - Description: Determines the sign of a number. + - Example: `hesabu.sign(-5)` returns `-1`. + +26. **sin(x)** + - Description: Calculates the sine of an angle in radians using the Taylor series. + - Example: `hesabu.sin(1)` returns `0.8414709848078965`. + +27. **sinh(x)** + - Description: Calculates the hyperbolic sine of a number. + - Example: `hesabu.sinh(0)` returns `0`. + +28. **sqrt(x)** + - Description: Calculates the square root of a number. + - Example: `hesabu.sqrt(4)` returns `2`. + +29. **tan(x)** + - Description: Calculates the tangent of an angle in radians. + - Example: `hesabu.tan(1)` returns `1.557407724654902`. + +30. **tanh(x)** + - Description: Calculates the hyperbolic tangent of a number. + - Example: `hesabu.tanh(0)` returns `0`. + +31. **factorial(n)** + - Description: Calculates the factorial of a number. + - Example: `hesabu.factorial(5)` returns `120`. + +32. **isNegative(num)** + - Description: Checks if a number is negative. + - Example: `hesabu.isNegative(-5)` returns `kweli`. + +33. **isInteger(num)** + - Description: Checks if a number is an integer. + - Example: `hesabu.isInteger(4.5)` returns `sikweli`. + +34. **getIntegerPart(num)** + - Description: Gets the integer part of a number. + - Example: `hesabu.getIntegerPart(4.5)` returns `4`. + +35. **list(first, last, interval)** + - Description: Creates a list of numbers with the specified interval between them. + - Example: `hesabu.list(1, 5, 1)` returns `[1, 2, 3, 4]`. + +36. **reduce(iterator, callback, initialValue)** + - Description: Reduces the elements of an array to a single value using a specified callback function. + - Example: `hesabu.reduce([1, 2, 3, 4], [callback function], 0)` + ```s + fanya callback = unda(accumulator, currentValue){ + rudisha accumulator + currentValue; + } + + andika(hesabu.reduce([1, 2, 3, 4], callback, 0)) \\ returns 10. +### Contributing + +Contributions to the `pakeji hesabu` package are welcome. If you have any improvements or bug fixes, feel free to create a pull request. + +### License + +This package is available under the MIT License. See the [LICENSE](LICENSE) file for more information. \ No newline at end of file diff --git a/third_party/math/hesabu.nr b/third_party/math/hesabu.nr new file mode 100644 index 0000000..ec113c8 --- /dev/null +++ b/third_party/math/hesabu.nr @@ -0,0 +1,533 @@ +tumia hisabati + +pakeji hesabu{ + //CONSTRUCTOR METHOD + andaa = unda() {} + + // Constants + // π (Pi) + PI = unda() { + rudisha 3.141592653589793; + } + + // e (Euler's Number) + e = unda() { + rudisha 2.718281828459045; + } + + // φ (Phi, Golden Ratio) + phi = unda() { + rudisha 1.618033988749895; + } + + // Natural logarithm of 10 + ln10 = unda() { + rudisha 2.302585092994046; + } + + // Natural logarithm of 2 + ln2 = unda() { + rudisha 0.6931471805599453; + } + + // Base 10 logarithm of Euler's number (e) + log10e = unda() { + rudisha 0.4342944819032518; + } + + // Base 2 logarithm of Euler's number (e) + log2e = unda() { + rudisha 1.4426950408889634; + } + + // √1/2 (equivalent to 1 / sqrt(2)) + sqrt1_2 = unda() { + rudisha 0.7071067811865476; + } + + // √2 (Square Root of 2) + sqrt2 = unda() { + rudisha 1.414213562373095; + } + + // √3 (Square Root of 3) + sqrt3 = unda() { + rudisha 1.732050807568877; + } + + // √5 (Square Root of 5) + sqrt5 = unda() { + rudisha 2.236067977499790; + } + + // @.EPSILON + EPSILON = unda() { + rudisha 0.0000000000000002220446049250313; + } + + // Methods + //abs(namba), calculates the absolute value of a number. + abs = unda(namba){ + kama(namba < 0){ + rudisha - 1 * namba; + } + + rudisha namba; + } + + //acos(x), calculates the arccosine of a number. + acos = unda(x) { + kama (x < -1 || x > 1) { + rudisha "NaN"; + } + + fanya EPSILON = 1*10.0**-10; // Small value for precision + + fanya acosRecursive = unda(guess) { + fanya f = cos(guess) - x; + fanya fPrime = -sin(guess); + fanya nextGuess = guess - f / fPrime; + + kama (abs(nextGuess - guess) < EPSILON) { + rudisha nextGuess; + } + + rudisha acosRecursive(nextGuess); + } + + rudisha acosRecursive(hisabati.PI() / 2); // Initial guess for acos + } + + //acosh(x), calculates the inverse hyperbolic cosine of a number. + acosh = unda(x) { + kama(x < 1) { + rudisha 0; + } + + rudisha log(x + sqrt(x * x - 1)); + } + + //asin(x), calculates the arcsine of a number using the Newton Method. + asin = unda(x) { + kama (x < -1 || x > 1) { + rudisha "NaN"; + } + + fanya maxIterations = 50; // Maximum number of iterations + + fanya newtonAsin = unda(guess, prev, iterations) { + fanya next = guess - (sin(guess) - x) / cos(guess); + + kama (abs(next - prev) < hisabati.EPSILON() || iterations >= maxIterations) { + rudisha next; + } + + rudisha newtonAsin(next, guess, iterations + 1); + } + + rudisha newtonAsin(x, 1, 0); + } + + + //asinh(x), calculates the inverse hyperbolic sine of a number. + asinh = unda(x) { + // Calculate arsinh using the formula: arsinh(x) = ln(x + sqrt(x^2 + 1)) + kama(x >= 0) { + rudisha log(x + sqrt(x * x + 1)); + } sivyo { + // For negative values, arsinh(x) = -arsinh(-x) + rudisha - log(-x + sqrt(x * x + 1)); + } + } + + //atan(x), calculates the arctangent of a number using the Taylor series. + atan = unda(x) { + fanya EPSILON = 1*10.0**-10; // Small value for precision + + fanya atanRecursive = unda(guess) { + fanya f = tan(guess) - x; + fanya fPrime = 1 / (cos(guess) * cos(guess)); + fanya nextGuess = guess - f / fPrime; + + kama (abs(nextGuess - guess) < EPSILON) { + rudisha nextGuess; + } + + rudisha atanRecursive(nextGuess); + } + + rudisha atanRecursive(x); // Initial guess for atan + } + + //atanh(x), calculates the inverse hyperbolic tangent of a number. + atan2 = unda(y, x) { + kama(x > 0) { + rudisha atan(y / x); + } au kama(x < 0 && y >= 0) { + rudisha atan(y / x) + hisabati.PI(); + } au kama(x < 0 && y < 0) { + rudisha atan(y / x) - hisabati.PI(); + } au kama(x == 0 && y > 0) { + rudisha hisabati.PI() / 2; + } au kama(x == 0 && y < 0) { + rudisha - hisabati.PI() / 2; + } au kama(x == 0 && y == 0) { + rudisha "NaN"; // Undefined + } + } + + //atanh(x), calculates the inverse hyperbolic tangent of a number. + atanh = unda(x) { + kama(x < -1 || x > 1) { + rudisha 0; + } + rudisha 0.5 * log((1.0 + x) / (1.0 - x)); + } + + //cbrt(x), calculates the cube root of a number. + cbrt = unda(x) { + kama(x == 0) { + rudisha 0; + } + + kama(x >= 0) { + rudisha root(x, 3); + } sivyo { + rudisha - root(-x, 3); + } + } + + //root(x, n), calculates the nth root of a number using the Newton-Raphson method. + root = unda(x, n) { + fanya guess = x / 2; // Initial guess + fanya tolerance = 0.0000000001; // Tolerance for convergence + + fanya calculateNthRoot = unda(x, n, guess, tolerance) { + fanya nextGuess = ((n - 1) * guess + x / (guess ** (n - 1))) / n; + fanya ipotolerance = abs(nextGuess - guess); + kama (ipotolerance < tolerance) {rudisha nextGuess}; + rudisha calculateNthRoot(x, n, nextGuess, tolerance); + } + + rudisha calculateNthRoot(x, n, guess, tolerance) + } + + //ceil(x), rounds up to the smallest integer greater than or equal to a given number. + ceil = unda(x) { + kama(x >= 0) { + kama(x % 1 == 0) { + rudisha x; // x is already an integer + } + rudisha floor(x) + 1; + } sivyo { + rudisha - floor(abs(x)); + } + } + + //cos(x), calculates the cosine of an angle. + cos = unda(x) { + fanya result = 1; // Initialize the result + fanya term = 1; + + kwa i ktk list(2,101,2) { + term = (-term * x * x) / (i * (i - 1)); + result += term; + } + rudisha result; + } + + //cosh(x), calculates the hyperbolic cosine of a number. + cosh = unda(x) { + fanya eToX = exp(x); + fanya eToMinusX = exp(-x); + rudisha(eToX + eToMinusX) / 2; + } + + //exp(x), calculates the value of Euler's number raised to the power of a given number. + exp = unda(n) { + fanya result = 1; + fanya term = 1; + + kwa i, v ktk list(1,23,1) { + term = term*(n/v); + result = result + term; + } + + rudisha result; + } + + + //expm1(x), calculates the value of Euler's number raised to the power of a given number minus 1. + expm1 = unda(x) { + kama (x == -1) { + rudisha -0.6321205588285577; // Handling the special case for -1 + } au kama (x == 0) { + rudisha 0; // Handling the special case for 0 + } au kama (abs(x) < hisabati.EPSILON()) { + rudisha x + 0.5 * x * x; // Approximation for very small x + } sivyo { + rudisha exp(x) - 1; + } + } + + + //floor(x), rounds down to the largest integer less than or equal to a given number. + floor = unda(x) { + kama(x >= 0) { + rudisha x - (x % 1); + } sivyo { + rudisha x - (1 + x % 1); + } + } + + //hypot(values), calculates the square root of the sum of squares of the given values. + hypot = unda(values) { + // Calculate the sum of squares of the values + fanya exp = unda(acc, value){ + rudisha acc + value ** 2; + } + + fanya sumOfSquares = reduce(values, exp, 0); + + // Calculate the square root of the sum of squares + fanya result = sqrt(sumOfSquares); + + rudisha result; + } + + //log(x), calculates the natural logarithm of a number. + log = unda(x) { + kama (x <= 0) { + rudisha "NaN"; + } + kama (x == 1) { + rudisha 0; + } + kama (x < 0) { + rudisha -log(-x); + } + fanya n = 1000; // Number of iterations + fanya y = (x - 1) / (x + 1); + fanya ySquared = y * y; + fanya result = 0; + kwa i ktk list(1,n+1,2) { + result += (1 / i) * y**i; + } + rudisha 2 * result; + } + + //log10(x), calculates the base 10 logarithm of a number. + log10 = unda(x) { + kama(x <= 0) { + rudisha 0; + } + + // Calculate natural logarithm and divide by the natural logarithm of 10 + rudisha log(x) / log(10.0); + } + + //log1p(x), calculates the natural logarithm of 1 plus the given number. + log1p = unda(x) { + kama (x <= -1) { + rudisha NaN; // Not a Number + } au kama (abs(x) < hisabati.EPSILON()) { + rudisha x - 0.5 * x * x; // Series expansion for small x + } sivyo { + rudisha log(1.0 + x); + } + } + + //log2(x), calculates the base 2 logarithm of a number. + log2 = unda(x) { + kama(x <= 0) { + rudisha 0; + } + + fanya result = 0; + fanya currentValue = x; + + wakati(currentValue > 1) { + currentValue /= 2; + result++; + } + + rudisha result; + } + + //max(numbers), finds the maximum value in a list of numbers. + max = unda(numbers) { + // Initialize a variable to store the largest number + fanya largest = 0; + + // Iterate through the numbers and update 'largest' kama a larger number is found + kwa num ktk numbers { + kama(num > largest) { + largest = num; + } + } + + // rudisha the largest number (or 0 kama there are no parameters) + rudisha largest; + } + + //min(numbers), finds the minimum value in a list of numbers. + min = unda(numbers) { + kama(numbers.idadi() == 0) { + rudisha 0; + } + + fanya minVal = numbers[0]; + + fanya i = 1; + wakati(i < numbers.idadi()) { + kama(numbers[i] < minVal) { + minVal = numbers[i]; + } + i++; + } + + rudisha minVal; + } + + //round(x, method), rounds a number to the nearest integer using the specified method. + round = unda(x, method = "ri") { + kama(method == "rpi") { + rudisha floor(x + 0.5); + } au kama(method == "rni") { + rudisha ceiling(x - 0.5); + } au kama(method == "ri") { + kama(x >= 0){ + rudisha floor(x + 0.5); + }sivyo{ + rudisha ceiling(x - 0.5); + } + } sivyo { + rudisha NaN; // Invalid method + } + } + + //sign(x), determines the sign of a number. + sign = unda(x) { + kama(x == 0 || x == -0) { + rudisha x; + } au kama(x > 0) { + rudisha 1; + } sivyo { + rudisha - 1; + } + } + + //sin(x), calculates the sine of an angle in radians using the Taylor series. + sin = unda(x) { + fanya result = x; // Initialize the result with the angle + fanya term = x; + // Using Maclaurin series expansion for sine + kwa i ktk list(3,101,2) { + term = (-term * x * x) / (i * (i - 1)); + result += term; + } + rudisha result; + } + + //sinh(x), calculates the hyperbolic sine of a number. + sinh = unda(x) { + // sinh(x) = (e^x - e^(-x)) / 2 + fanya eToX = exp(x); + fanya eToMinusX = exp(-x); + rudisha(eToX - eToMinusX) / 2; + } + + //sqrt(x), calculates the square root of a number. + sqrt = unda(x) { + kama(x < 0) { + rudisha 0; + } + kama(x >= 0) { + rudisha root(x, 2); + } sivyo { + rudisha - root(-x, 2); + } + } + + //tan(x), calculates the tangent of an angle in radians. + tan = unda(x) { + fanya sineX = sin(x); + fanya cosineX = sqrt(1 - sineX * sineX); + + kama(cosineX == 0) { + rudisha 0; + } + + rudisha sineX / cosineX; + } + + //tanh(x), calculates the hyperbolic tangent of a number. + tanh = unda(x) { + fanya expX = exp(x); + fanya expNegX = exp(-x); + rudisha(expX - expNegX) / (expX + expNegX); + } + + // utility methods + //factorial(n), calculates the factorial of a number. + factorial = unda(n) { + kama(n == 0){ + rudisha 1; + }; + fanya result = 1; + fanya i = 1; + + wakati(i <= n) { + result *= i; + i++; + } + + rudisha result; + + } + + //isNegative(num), checks if a number is negative. + isNegative = unda(num) { + rudisha sign(num)==-1; + } + + //isInteger(num), checks if a number is an integer. + isInteger = unda(num) { + rudisha num == floor(num); + } + + //getIntegerPart(num), gets the integer part of a number. + getIntegerPart = unda(num) { + // Handle negative numbers separately + kama(isNegative(num)) { + // For negative numbers, we subtract the absolute value of the fractional part from 1 + rudisha - (ceil(-num) - 1); + } sivyo { + // For positive numbers, we simply truncate the fractional part + rudisha floor(num); + } + } + + //Arrray Methods + //list(first, last, interval), creates a list of numbers with the specified interval between theM. + list = unda(first, last, interval){ + fanya list = [first]; + fanya i = first + interval; + wakati(i < last){ + list.sukuma(i); + i += interval; + } + rudisha list; + } + + //reduce(iterator, callback, initialValue), reduces the elements of an array to a single value using a specified callback function. + reduce = unda(iterator, callback, initialValue) { + fanya accumulator = initialValue; + + kwa thamani ktk iterator { + accumulator = callback(accumulator, thamani); + } + + rudisha accumulator; + } +} diff --git a/third_party/math/test.nr b/third_party/math/test.nr new file mode 100644 index 0000000..29b9895 --- /dev/null +++ b/third_party/math/test.nr @@ -0,0 +1,41 @@ +tumia "hesabu" + +andika("abs: ",hesabu.abs(-42)); +andika("acos: ",hesabu.acos(0.5)); +andika("acosh: ",hesabu.acosh(2)); +andika("asin: ", hesabu.asin(0.5)); +andika("asinh: ",hesabu.asinh(2)); +andika("atan: ",hesabu.atan(1)); +andika("atan2: ",hesabu.atan2(1, 1)); +andika("atanh: ",hesabu.atanh(0.5)); +andika("cbrt: ",hesabu.cbrt(8)); +andika("root: ",hesabu.root(27, 3)); +andika("ceil: ",hesabu.ceil(4.3)); +andika("cos: ",hesabu.cos(5)); +andika("cosh: ",hesabu.cosh(5)); +andika("exp: ",hesabu.exp(2)); +andika("expm1: ",hesabu.expm1(1)); +andika("floor: ",hesabu.floor(4.7)); +andika("hypot: ",hesabu.hypot([3, 4])); +andika("log: ",hesabu.log(2)); +andika("log10: ",hesabu.log10(100)); +andika("log1p: ",hesabu.log1p(1)); +andika("log2: ",hesabu.log2(8)); +andika("max: ",hesabu.max([4, 2, 9, 5])); +andika("min: ",hesabu.min([4, 2, 9, 5])); +andika("round: ",hesabu.round(4.6, "rpi")); +andika("sign: ",hesabu.sign(-5)); +andika("sin: ",hesabu.sin(1)); +andika("sinh: ",hesabu.sinh(0)); +andika("sqrt: ",hesabu.sqrt(4)); +andika("tan: ",hesabu.tan(1)); +andika("tanh: ",hesabu.tanh(0)); +andika("factorial: ",hesabu.factorial(5)); +andika("isNegative: ",hesabu.isNegative(-5)); +andika("isInteger: ",hesabu.isInteger(4.5)); +andika("getIntegerPart: ",hesabu.getIntegerPart(4.5)); +andika("list: ",hesabu.list(1, 5, 1)); +fanya callback = unda(accumulator, currentValue){ + rudisha accumulator + currentValue; +} +andika("reduce: ",hesabu.reduce([1, 2, 3, 4],callback,0) ); diff --git a/token/token.go b/token/token.go index e9f006d..18c4f6a 100644 --- a/token/token.go +++ b/token/token.go @@ -1,3 +1,5 @@ +// This is where we define our tokens + package token type TokenType string @@ -5,6 +7,7 @@ type TokenType string type Token struct { Type TokenType Literal string + Line int } const ( @@ -15,18 +18,33 @@ const ( IDENT = "KITAMBULISHI" INT = "NAMBA" STRING = "NENO" + FLOAT = "DESIMALI" // Operators - ASSIGN = "=" - PLUS = "+" - MINUS = "-" - BANG = "!" - ASTERISK = "*" - SLASH = "/" - LT = "<" - GT = ">" - EQ = "==" - NOT_EQ = "!=" + ASSIGN = "=" + PLUS = "+" + MINUS = "-" + BANG = "!" + ASTERISK = "*" + POW = "**" + SLASH = "/" + MODULUS = "%" + LT = "<" + LTE = "<=" + GT = ">" + GTE = ">=" + EQ = "==" + NOT_EQ = "!=" + AND = "&&" + OR = "||" + PLUS_ASSIGN = "+=" + PLUS_PLUS = "++" + MINUS_ASSIGN = "-=" + MINUS_MINUS = "--" + ASTERISK_ASSIGN = "*=" + SLASH_ASSIGN = "/=" + MODULUS_ASSIGN = "%=" + SHEBANG = "#!" //Delimiters COMMA = "," @@ -38,21 +56,33 @@ const ( LBRACKET = "[" RBRACKET = "]" COLON = ":" + DOT = "." + AT = "@" // Keywords FUNCTION = "FUNCTION" - LET = "ACHA" + LET = "FANYA" TRUE = "KWELI" FALSE = "SIKWELI" IF = "KAMA" ELSE = "SIVYO" RETURN = "RUDISHA" WHILE = "WAKATI" + NULL = "TUPU" + BREAK = "VUNJA" + CONTINUE = "ENDELEA" + IN = "KTK" + FOR = "KWA" + SWITCH = "BADILI" + CASE = "IKIWA" + DEFAULT = "KAWAIDA" + IMPORT = "TUMIA" + PACKAGE = "PAKEJI" ) var keywords = map[string]TokenType{ - "fn": FUNCTION, - "acha": LET, + "unda": FUNCTION, + "fanya": LET, "kweli": TRUE, "sikweli": FALSE, "kama": IF, @@ -60,6 +90,17 @@ var keywords = map[string]TokenType{ "sivyo": ELSE, "wakati": WHILE, "rudisha": RETURN, + "vunja": BREAK, + "endelea": CONTINUE, + "tupu": NULL, + "ktk": IN, + "kwa": FOR, + "badili": SWITCH, + "ikiwa": CASE, + "kawaida": DEFAULT, + "tumia": IMPORT, + "pakeji": PACKAGE, + "@": AT, } func LookupIdent(ident string) TokenType { diff --git a/upx b/upx new file mode 100755 index 0000000..6fda7fb Binary files /dev/null and b/upx differ