🐍 I made my own version of Python in C! 🐍 + ⓒ
You know those "Python interpreters" which are really just eval(input())
(or exec(input())
)? Well, I decided to make the polar opposite of that — a ground-up custom implementation of Python in C with nothing more than standard libc. 1191 semicolons and 2252 lines later, I've gotten my interpreter to a usable state.
A quick disclaimer: this isn't finished yet! I'm sharing it now since it's in a usable state, but it doesn't (yet) implement things like OOP, dictionaries or arrays. I'll share updates when I make them!
Changes
Of course, what fun would an exact remake be? To shake things up a bit, I've made some changes:
- Function calls don't have parens. So
function 1, 2, 3
is likefunction (1, 2, 3)
in normal Python. This means that something like(int x + 1)
is sayingint (x + 1)
, so be careful with your parentheses! (If you intend that to not happen, do(int x) + 1
.) - For a function call with no arguments, use
()
as the argument. (()
is actually a synonym forNone
.) - Arguments are handled with
my
. This means that instead of doing:
def myfun(x, y, z):
You do: ```Python def myfun: my x my y my z <actions>
Now, what point does this have you ask? Well, you can put the my
statement anywhere. Inside loops, conditionals, you name it. So you could do:
def myfun: i = 1 while i < 10: my x print "The #" + (str i) + " argument is: " + (str x) i = i + 1
Limitations
- No for loops. For loops require iterators, which don't exist yet.
- No
%
or+=
,-=
, etc. - No
elif
— useelse
&if
, and nobreak
/continue
- No
True
orFalse
— use1
and0
- No
float
So yeah, it's limited as of now, but it works!
Demo
Here's a demo (look in source.fpy
in the repl for more):
def fib: my x if x < 3: return 1 return (fib x-1) + (fib x-2) def factorial: my x if x == 1: return x return x * (factorial x-1) userExiting = 0 while userExiting == 0: print "Welcome to the calculator! What would you like to do?\n\ 1 - Exit\n\ 2 - Calculate factorial\n\ 3 - Calculate fibonacci" userInput = input ">> " if userInput != "1" and userInput != "2" and userInput != "3": print "Invalid option! Try again." else: userInput = int userInput if userInput == 1: userExiting = 1 if userInput == 2: userInput = int (input "What number? ") print "Factorial of " + (str userInput) + " is " + (str (factorial userInput)) if userInput == 3: userInput = int (input "What number? ") print "The #" + (str userInput) + " fibonacci number is " + (str (fib userInput))
How can I get started?
Just fork the repl and edit source.fpy
(a demo program is preloaded)! Hit run and you'll be good to go!
I hope you enjoy! There's probably numerous bugs (if you find one please report it!) but so far it's worked fairly well!
Note: It's been asked if the build folder was auto-generated. The answer is no — I wrote all the code in this project! I simply put code in the build folder to make the repl easier to navigate.
wait hold up do you not know that
#pragma once
is to help make you unsad?
#pragam once
isn't in the standard because of some weird file semantics. I think one problem is when files contain the same name but are in different directories, pragma can't tell which is which#ifndef thefile_h
-style because it's standard and guaranteed to work. And it's really not that big of a difficulty compared to #pragma once
.yeah it is determined by if your compiler supports it or not, but I do know the latest versions of GCC, Clang, and MSVC work.
@DynamicSquid#pragma
#pragma once
is useless, I only used it in the past since I was stupid and thought that I would have to do checks for each of the stdlib headers.the only reason I am using it is because that was how I was taught
ig the ifndef
is better idk
If it were standard, I doubt anyone would willingly not use it, unless you were trying to support an old compiler or a compiler that didn't support it.
and the only reason I would be using an older compiler is if I wanted to hurt myself lol
@xxpertHackerThe code won't work outside of the compilers that support it.
(btw, Rust don't have these problems)
and the only reason I would be using an older compiler is if I wanted to hurt myself lol
100% agree there, I would never use an older compiler than a C++20 compiler. I hate how Repl still has 2017 compilers.
I think I have a /feedback or /bugs report requesting an update; they didn't notice it.
(Legit the reason I dislike Repl, it gets outdated fast, and they don't update anything, ever)
I also though that pragma has many problems with it.
I recently checked out compiler support, just this week, but I don't remember where it is right now; I could probably look it up real fast.
As for pragma
, it's just as I had said, it's simply non-standard, but supported.
pragma
though. Although the 4th answer suggests using both, but I think that's just too ugly. But yeah, I guess you're right, the bugs are supper rare.
:( C++ is adding too many features. I guess modules are nice, coroutines sound very interesting actually (heard they're really good for networking and server requests), but concepts? I don't really feel the need for more template metaprogramming. Aggregate initialization using ()
doesn't make and sense... and polymorphic allocator? What's that?
Finally got back, I was checking out the GNU stdlib implementation, and I happened across their C++20 support.
https://gcc.gnu.org/projects/cxx-status.html
And the other compilers have good support too, but they didn't support modules yet, but some others already do.
https://en.cppreference.com/w/cpp/compiler_support/20
But yeah, I guess you're right, the bugs are super rare.
Umm... I never suggested that?
I was saying that #pragma once
is non-standard, but supported in certain compilers. That's it.
If you use it, just know that it might not work (as you expect it to), and that it might not be portable.
But among the compiler vendors who do support it, it may be more optimized than manually adding your own macro guards.
Concepts are similar to Rust traits; they provide constraints to generic code. I haven't gotten my hands on them yet though, but I'm not too sure I'll care until I have them.
Now coroutines
and co_await
, oh, now that is going to be fun.
polymorphic allocator
I frankly have no clue what that could mean, but then again, I'm not about to try to disambiguate it either.
I have a feeling that it allows for more performant code in specific situations since that's why plenty of ad-hoc allocators are used, but maybe not.
If modules are as good as they claim to be, I'll come back to C++. (I really hope they are)
Wait no, I need Repl.it to update their dang compiler!
We're almost in 2021, why am I forced to use a buggy 2017 compiler?
std::variant
. I guess I'm more of a minimalist and only use features if I have to. I probably should start doing what you're doing though and use some of the newer features.But, I have wanted modules forever.
I've used over half of what C++17 offers.
Allow typename (as an alternative to class) in a template template parameter
I only use typename
, never class
.
Nested namespace definitions, e.g., namespace X::Y { … } instead of namespace X { namespace Y { … } }
Used it earlier this month.
New standard attributes [[fallthrough]], [[maybe_unused]] and [[nodiscard]]
Ha, I think you saw me use one.
UTF-8 (u8) character literals[14][17] (UTF-8 string literals have existed since C++11; C++17 adds the corresponding character literals for consistency, though as they are restricted to a single byte they can only store ASCII)
I tried it out, it was pretty lame :)
Hexadecimal floating-point literals
I think C++ is the only language that I've used that allows that, I'ma put that to use soon.
Use of auto as the type for a non-type template parameter
Check.
Constant evaluation for all non-type template arguments
Check.
Fold expressions, for variadic templates
Probably used it... somewhere?
A compile-time static if with the form if constexpr(expression)
Check.
Structured binding declarations, allowing auto [a, b] = getTwoReturnValues();
Caused what I belive to be undefined behavior in Clang-7 with that ^, still liked it.
Initializers in if and switch statements
I love this feature right here ^
Class template argument deduction (CTAD), introducing constructor deduction guides, eg. allowing std::pair(5.0, false) instead of requiring explicit constructor arguments types std::pair<double, bool>(5.0, false) or an additional helper template function std::make_pair(5.0, false)
Used a deduction guide this year, I think I used more than one.
Actually, that reminds me... I have to go help someone out with this.
Inline variables, which allows the definition of variables in header files without violating the one definition rule. The rules are effectively the same as inline functions
__has_include, allowing the availability of a header to be checked by preprocessor directives
Check.
Exception specifications were made part of the function type
Check.
std::string_view
Check. Oh yeah, they allow creating 0 allocation slices too!
Okay, I have used way more than even what has been listed so far, but still.
Generally, I tend to prefer bleeding-edge technology.
Give me a new compiler with new features, I'm going to try everything out, what's good, I'll use, what's useless, I'll discard, what I don't understand yet, or don't have need for yet, I'll reserve for later.
Allow typename (as an alternative to class) in a template template parameter
Yay! They finally fixed that! Now I don't have to worry about the class
keyword in templates
New standard attributes [[fallthrough]], [[maybe_unused]] and [[nodiscard]]
Not really a big fan of attributes :( I feel like good code doesn't need these. Comments are better. But I guess if you can't suppress compiler warnings than these could be pretty useful.
UTF-8 (u8) character literals[14] (UTF-8 string literals have existed since C++11; C++17 adds the corresponding character literals for consistency, though as they are restricted to a single byte they can only store ASCII)
I think I used that when I was making some stuff with console graphics before
Fold expressions, for variadic templates
Okay now that just gets confusing
Initializers in if and switch statements; A compile-time static if with the form if constexpr(expression)
And I thought if statements were as simple as it can get lol
Class template argument deduction (CTAD), introducing constructor deduction guides, eg. allowing std::pair(5.0, false) instead of requiring explicit constructor arguments types std::pair<double, bool>(5.0, false) or an additional helper template function std::make_pair(5.0, false)
I used that a lot without even realizing it. Java has this feature so I guess I got used to it
I bet you C++50 will look like this:
std::make_language(); std::make_website(); std::make_google(); std::make_falcon_9_rocket();
Ohh, you know what I haven't done? constexpr if
+ initialization in if.
constexpr if (constexpr bool x = false; x);
That sounds both, terrible, and good, at the same time.
That could be abused so badly.
Now, as for attributes, they're not for other programmers, they're for giving extra meta info to the compiler.
You can have 100 comments telling other people how the code should work, but not one of them will be read by the compiler at all.
Attributes help generate optimal code, without creating a whole new syntax within the language.
Go lookup C++20's likely
and unlikely
attributes, and you'll understand their purpose better.
I tried Rust (made a language in it actually) but didn't like the whole memory transfer thing.
Crystal seems really good (I saw the website and instantly got hooked) but I haven't tried it yet. But this weekend actually I might create a language with it.
Welp, I guess I have to make my own, or you have any suggestions?
But if you can't, then you'll have to keep searching.
(I can't make my own lang, langdev is hard, and I suck at naming stuff)
Btw I checked out Mirror's source some time ago.
What are you currently working on?
Right now, I'm helping update a text editor to properly support encodings: https://encoding-pr.xxperthacker.repl.co.
I posted it in "share," but I think you were the only one to upvote it.
It was okay until today, when I realized something terrible was happening.
It reads a file correctly... but mauls the encoding when writing.
So, if you use it, it just mauls your file system, one file at a time, without you even realizing it :).
There are also some edge cases that cause data loss.
I need to fix it soon.
Beyond that, I was thinking of getting back into language development, but I still don't know how the fundamentals would work, I still need an answer to this Q: Why do references exist? Should we only have pass-by-value?.
And I don't want to use C++ till I understand how this went wrong, lol.
Also you should try Java. Java's all pass by value.
Java is also a garbage-collected language. (the language wouldn't need a GC if the language weren't garbage)
Also all the problems with pointers you're having won't be a problem in Java
Oh, I wouldn't be getting segfaults, I'd just be getting range errors for accessing out of bounds data, really, it's no different, the logic error remains
Absolutely agree lol.
Hmm... I'm not sure about your problem. Have you tried using asserts?
garbage collection go brr
@DynamicSquidthe language wouldn't need a GC if the language weren't garbage
LOL so true
And yes, I hope C++ dies soon. It feels really... bloated. They're trying to add too much and it's just getting messy.
As for Concepts, those would actually be kinda cool, but I don't know how much practical value they have. As xxpertHacker said, they're like Rust's traits or Haskell's typeclasses, but I feel like C++ doesn't really need that.
@fuzzyastrocat
I'm honestly surprised that neither of you hated on me for being too harsh towards Java, but okay, cool, we're all on the same page.
C++ is a pretty neat language, but it's weighing itself down, this is why I fully advocate using Rust instead, it's a might lighter-weight language, built in the modern-day and age, for the modern-day and age, today, not 30-40 years ago.
I wish someone could just cast off what C remains in C++, but instead it'll stay :(.
I want the same thing for all languages that have survived.
Why use Python 2 when you have Python 3? Python 3 made backwards incompatible changes, and I like that.
I hate seeing how JavaScript still has so much old stuff, just remove the old parts and it's actually a great scripting language, like seriously, why are there still both, var
and let
!? One of them needs to go, and I'm all for removing the older one.
If C++ actually chose to do the same, it wouldn't be as bad, but it would require a major overhaul.
Eventually the same may happen to all great languages (*cough, Rust), and that'll be a sad day indeed.
And then there's the stdlib, that's even larger.
And I doubt that I could do it without introducing my own bias' either.
(Though your anecdote about var
and let
— those two keywords serve different purposes, and I use both purposes often.)
var
& let
are both used to declare reassignamble variables.Let
enforces lexical scoping, var
doesn't, and it's hoisted.
Var
is deprecated, you should never use it unless you're working with a transpiler+minifier.
Why do you use it?
function do_some_stuffs(){ do { var my_var = ...; // some code involving my_var } while (/* some condition involving my_var */); }
I know it's inside a function, so var
doesn't leak out of scope. And yes, I could do it with let
, but that would mean an extra line of code and now when I'm re-reading it I'm wondering where I assign to my_var
(since this example is small that might not be apparent, but in larger code examples it makes more sense).
function do_some_stuffs() { do { var my_var = ...; // some code involving my_var } while (/* some condition involving my_var */); return my_var; // edited in }
The way I read this, when I get to the return
, I would check the variables outside of the loop's scope, and if there aren't any, then I'd check the arguments. If there aren't any, then I would scan the entire function and global scope for this variable that appears from nowhere.
This only happens to those who are used to let
/const
though.
But in reality, recursion is the proper way to loop over anything.
my_var
is included in anything outside the loop then yes, I agree that it should be declared with let.
(And yes, FP is best)
(Oh wow thanks, didn't realize I had reached it exactly!)
Btw, I'm thinking of allowing features that somewhat stand in between normal imperative, mutable code, and FP.
Think of a simple for
loop, they're often used to reassign variables with every iteration, until a specific value is reached, or a condition is met.
Recursion does the exact same thing, but instead of reassignment, it passes the current iteration's values back to itself.
I'm thinking of something else, how about, instead of recursion, which is inefficient (without optimization), or loops reading and writing to registers (which is against FP, and could be inefficient), the author just pushes and pops values off of the stack, repeatedly, until a condition is met.
It would act like inline recursion, acting somewhere in between both of the opposing ideas.
I'm thinking that it would require some sort of variables, I'm probably going to call them "loop-locals."
An example, with a not planned out syntax:
// on entering the loop, the loop-locals are assigned these values let sum = loop(i = 0, counter = 1) { if (i < 10) { continue(i + 1, counter * 10); // leaves both values on the stack } else { break(null, counter); // leaves only counter on the stack } }
When the bottom of the loop is reached, a goto/jmp is done, moving control flow back to the top, but leaving the values on the stack.
But the problem: that syntax is god-awful, any suggestions?
Does this sound like a good idea to implement in a human-readable language? (this sounds more like Asm tbh)
If done right, this would be very expression-orientated, more so than Rust's loops.
But python already exists! Lol!
Of course, what fun would an exact remake be? To shake things up a bit, I've made some changes:
This is GREAT NGL
@fuzzyastrocat :)That would be rilly cool
@fuzzyastrocat I would love to see it
Bruh, how did u get so good. All your projects are frickin amazing
Wait its 667 now XD