-
Notifications
You must be signed in to change notification settings - Fork 1
Appcypher-syntax.md #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
|
||
| ``` | ||
| let name = "Okechukwu" | ||
| ``` | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with using let for declaration of constant variables. Though const is explicit it is used in so many languages and it a bit verbose even though it just one character extra of let.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const has two extra characters. And I find it aesthetically inconsistent with the three-letter var.
var name = "John"
const date = "20-11-2019"
var make = "Camaro"vs
var name = "John"
let date = "20-11-2019"
var make = "Camaro"There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.... However i'd say const is more descriptive. We can debate on more points later though
| #### Shadowing | ||
| - A subject can be rebound to a different type within the same scope. | ||
| ```js | ||
| let phone = "08074789222" // `phone` has type `String` here | ||
| let phone = 08074789222 // `phone` now has type `Int` | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some restrictions on variable shadowing, a constant variable should not be shadowable by a new variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain your thoughts here? Is there a reason shadowing constants doesn't make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the @Thecarisma . This kind of shadowing can be done for mutable variables only. Then the implementation of this will require the modification of the AST in a multi-pass sequence or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No one has given any compelling reason though.
I would like to know if shadowable constant can make compiler implementation hard or if it is going to lead to some unwanted consequences or sth else?
Constructive feedback is important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shadowing constant will break the main concept of a constant itself, a constant should only be defined once.
Also for the compiler implementation, we will have an extra rule of check on a variable that shadows a constant to ensure the scope is different instead of just breaking the process.
Also, it ok with me for an inner function to shadow a constant in the parameter but not in the body of the function.
| #### Signed Integers | ||
| - Int - Pointer-sized signed integer | ||
| - I8 - 8-bit signed integer | ||
| - I16 - 16-bit signed integer | ||
| - I32 - 32-bit signed integer | ||
| - I64 - 64-bit signed integer | ||
| - Int - Pointer-sized integer | ||
|
|
||
| #### Unsigned Integers | ||
| - UInt - Pointer-sized unsigned integer | ||
| - U8 - 8-bit unsigned integer | ||
| - U16 - 16-bit unsigned integer | ||
| - U32 - 32-bit unsigned integer | ||
| - U64 - 64-bit unsigned integer | ||
|
|
||
| #### Floating point numbers | ||
| - F32 - 32-bit IEEE 754-2019 floating point representation | ||
| - F64 - 64-bit IEEE 754-2019 floating point representation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am in absolute support of having the primitive types of different bit sizes, other types can be created from the primitive type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very plausible and i agree with this totally!
| ## TUPLES | ||
| - Tuple is a special datatype with statically-known length. It allows for cool features like arguments unpacking. | ||
|
|
||
| ```swift | ||
| var fave_game = ("Uncharted 4", 2016) | ||
| ``` | ||
|
|
||
| One-element tuple. Note the trailing comma. | ||
|
|
||
| ```rust | ||
| let map = (london,) | ||
| ``` | ||
|
|
||
| Empty tuple. | ||
|
|
||
| ```swift | ||
| let empty_tuple = () | ||
| ``` | ||
|
|
||
| - Tuple indexing. Tuple index space starts at index 0. | ||
|
|
||
| ```rust | ||
| let book = ("Harry Potter", "J.K. Rowling", 1995) | ||
| let title = book.1 // Harry Potter | ||
| let author = book.2 // J.K. Rowling | ||
| let year = book.3 // 1995 | ||
| ``` | ||
|
|
||
| - Tuple destructuring. | ||
|
|
||
| ```rust | ||
| let person = ("Sam", 50) | ||
| let (name, age) = person | ||
| ``` | ||
|
|
||
| - Tuple unpacking. | ||
|
|
||
| ```swift | ||
| func add(a, b) { | ||
| a + b | ||
| } | ||
|
|
||
| let arguments = (5, 6) | ||
| add(...arguments) | ||
| ``` | ||
|
|
||
| - Tuples have limitations because they are expected to be amenable to static analysis. | ||
|
|
||
| ```swift | ||
| var tup1 = (1, 2) | ||
| var tup2 = (5, 6) | ||
| ``` | ||
|
|
||
| Directly or indirectly, a tuple can't be used to extend itself. | ||
|
|
||
| ```go | ||
| for i in range(x) { | ||
| tup2 = (...tup2, ...tup1) // Invalid! | ||
| } | ||
|
|
||
| for i in range(x) { | ||
| tup3 = (...tup2, 1) | ||
| tup2 = (tup3, tup1) // Invalid! | ||
| } | ||
| ``` | ||
|
|
||
| - The type of a tuple. | ||
|
|
||
| Since tuples can't change in size, a tuple has a fixed type. For example the tuple `(0xFF, "Hi")` has type `(Int, String)`. `("Hello", 5.0, "Hi")` has type `(String, F64, String)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not support the idea of tuple, I believe a tuple is simple an immutable list just that tuple are created with bracket.
We can have a tuple as a high-level type that accepts variadic and any type as the parameter which uses methods and function instead of built-in language feature. e.g as a function
var fave_game = tupple("Uncharted 4", 2016)as a type
var fave_game = Tupple::new("Uncharted 4", 2016)All the rules guiding a proper tuple can be put in place on the high-level Tuple type.
The idea of unpacking a tuple is great as I find it the ultimate usefulness of a tuple so we can add the feature to list i.e a list can be unpacked for use as parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well tuple isn't just an immutable list here. Immutable list is easily implemented in the language.
The reason tuple has to be special here is because it has compile-time properties that makes it useful for certain scenarios.
1.) It has a length known at compile-time.
2.) It has element types known at compile-time.
And those are the reasons we can't just make tuple an immutable list. List length, immutable or not, are not always statically known.
let length: Int = input_from_user()
let values = List::new(length)In the example above, we don't know the list length at compile-time, because length is only provided at runtime.
Also, for a heterogenous list, the individual element types are not always statically known.
let samples: [String|Int] = []
for i in 0..len {
samples.push(
if cond { "Hi" } else { 20 }
)
}
let values: [String|Int] = samplessamples can contain both String and Int elements, but it is very possible that at the end of the loop, there is no item in samples; that samples contain only String items; that samples contain only Int items; that samples contain items of both types in any order.
let samples: [String|Int] = []
for i in 0..len {
samples.push(
if cond { "Hi" } else { 20 }
)
}
let values: [String|Int] = samples
func show(a: Int, b: Str) -> {
print(a², b.upper())
}
show(...values) # Invalid! We can't unpack values here because values can have any length here, and its elements can be of any type. But we need those information at compile-time to correctly type-check the function. This is why immutable list is unsuitable for this.
On the other hand, a tuple has this information available at compile-time. The individual element types and the length of the tuple are known at compile time.
But being able to unpack a tuple as function arguments isn't the only benefit of here. Tuples are more efficient than Lists memory-wise and perf-wise (reduces cache misses). Tuples are like structs, except indexable by integer and suitable for unpacking.
This is how Rust and Swift tuples work. And this is also how C++ tuples work, although the tuple is implemented behind a variadic template type.
| ## RANGE | ||
| - Range is an iterable that allows iterating through a range of values. | ||
|
|
||
| ```swift | ||
| let days = 1..31 // 1 to 30 | ||
| ``` | ||
|
|
||
| - A range can have a step value. | ||
|
|
||
| ```swift | ||
| let evens = 2..2..20 // The step value is the `2` in the middle. | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For range between values we should use the colon operator to indicate the range e.g
let days = 1:31
and a range with step value
let events = 2:2:20
For the step, it easily understandable to have the step value at the end of the range expression.
let events = 2:20:2 //the 2 at the end is the step value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This conflicts with the generics application syntax.
func new[T]() -> Self {
// ...
}
let buf = Buffer::new:[Int]() // Generics applicationThe reason I chose this generics syntax over the typical Buffer::new<Int>() syntax, is that it keeps our grammar in the CFG zone. C++ parser is complicated for a reason and it is because it is context-aware which sucks. It makes parser really hard to implement, and language grammar impossible to fully specify with EBNF, PEG and what not.
Here is why C++ template syntax is context aware. It's easy for the parser to confuse a '<' as a lesser than operator, so it requires information that are only available down the compilation line to make sense of the data. In this case, I believe it requires type information to make the distinction and properly parse it.
a < b > ( 1 )This is ambiguous. It can be interpreted as (a) < (b) > (1) or as generics application a<b>(1) where b is a type.
So languages like Rust add a distinction factor. Rust's generics application syntax starts with ::. So instead of Buffer::new<Int>(), you write Buffer::new::<Int>(). They call it turbofish, because it looks like a fish. ::<>
I could have gone with the same turbofish, but I don't find it aesthetically pleasing, so I went for more succinct Buffer::new:[Int]()
So that is where the : operator is used, and it will be ambiguous to use it for range as well.
PS: I guess we could make an exception here. Any: followed by a [ could be parsed as generics application, otherwise it is parsed as range.
| ## STRINGS | ||
| - Strings are a sequence of valid UTF-8 codepoints and they are delimited by double quotes `"`. | ||
|
|
||
| ```swift | ||
| let language = "Ratio" | ||
| ``` | ||
|
|
||
| - String interpolation is done with a pair of curly braces | ||
| ```js | ||
| var story = "{language} was started in the {year}" | ||
| ``` | ||
|
|
||
| - String operations | ||
|
|
||
| ```js | ||
| var concatenate = "ab" + "c" // "abc" | ||
| var multiply = "ab" * 2 // "abcabc" | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should model the string more like a high-level type and not a primitive type, the operator behaviour should be determined by the operator overloading methods of the string type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String is not a primitive type. There are no notion of primitiveness in my proposal.
There are
- scalar or primitive types, which are machine-level types because your CPU can operate on them efficiently. These are types that are 1 to 8-byte wide. Basically, the builtin integer and floating-point types.
- high-level or complex types, which build on scalar types
+ and * are overloaded for the String type. String is a high-level type because it builds on scalar types. Implementation-wise, a string type would look like this.
type String {
length: Int,
capacity: Int,
buffer: *U8, // pointer to UTF-8 buffer
}| ## UNSAFE BLOCK | ||
| - The unsafe block is necessary for scoping the usage of unsafe/unstable features like pointer arithmetic and destructive casting. | ||
|
|
||
| ```rust | ||
| let base_ptr = some_ref as *Int | ||
|
|
||
| let base_ptr = unsafe { | ||
| base_ptr.add(20) | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no no, the pointer is a torture feature. We handle the memory within the language, we might add the pointer and memory access feature but personally, pointers scare me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Go, Rust, Swift, Java all have pointers. Rust and Go expose them in a safe way. That is no pointer arithmetic, which may cause pointer aliasing problems. For unsafe stuff like pointer arithmetic, a lot of languages just lets you shoot yourself in the foot, but Rust does the right thing here with unsafe blocks. It limits the bug surface area, but you can still shoot yourself in the foot.
Unfortunately, when you need to go low-level, or interface C libraries, you need to have pointers in the language! If this is going to be a statically-typed language, pointers need to provided be otherwise you won't be able to do a lot of low-level shenanigans.
| ## GENERICS | ||
| - Declaring a generic type | ||
|
|
||
| ```rust | ||
| type Buffer[T] { | ||
| buffer: *T, | ||
| capacity, | ||
| len | ||
| } | ||
| ``` | ||
|
|
||
| - Declaring a generic function | ||
|
|
||
| ```swift | ||
| impl Buffer[T] where T: Integer { | ||
| func new[T]() -> Self { | ||
| Self { | ||
| buffer: allocator:[T](10), | ||
| capacity: 10, | ||
| len: 0, | ||
| } | ||
| } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The generic type will be used to create super types like tuple, sting e.t.c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I already replied on those
| ## IMPORT | ||
| - Ratio is a module-based language. To use things from other modules, you have to import from them. | ||
|
|
||
| ```py | ||
| import .dir {foo} | ||
| ``` | ||
|
|
||
| - Ratio has a special file called `index.ratio`. It is used to expose modules in a particular folder. Modules of a folder can only be accessed if the folder has an `index.ratio` that exposes them. | ||
|
|
||
| ```py | ||
| import .dir | ||
| ``` | ||
|
|
||
| If there is a folder called `dir`, this import can only be valid if there is a file path `./dir/index.ratio` | ||
|
|
||
| If there is no folder called `dir`, then there has to be a file `./dir.ratio` | ||
|
|
||
|
|
||
| - Ratio has two kinds of imports. | ||
|
|
||
| #### Regular relative import | ||
|
|
||
| The imported module is relative to the source file defining the import. The import symbol is preceded by a dot. | ||
|
|
||
| ```py | ||
| import .dir | ||
| ``` | ||
|
|
||
| `dir` here can either be `./dir.ratio` or `./dir/index.ratio` | ||
|
|
||
| #### Root relative import | ||
|
|
||
| The imported module is relative to the project's root folder or it is some external dependency. | ||
|
|
||
| ```go | ||
| import dir | ||
| ``` | ||
|
|
||
| `dir` here can either be `src/dir.ratio` or `src/dir/index.ratio` or some external dependency named `dir` with a path `src/index.ratio`. | ||
|
|
||
| - Ratio supports aliasing an imported element ' | ||
|
|
||
| ```py | ||
| import ext {foo} | ||
| import .module.some {foo as bar} | ||
|
|
||
| foo() | ||
| bar() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the import, we should only use the last approach and eliminate the use of . before a module path. Any module imported should be looked up in the relative folder, then other paths where the language module can be such as the project root folder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to cause unintended behavior. I'm afraid, Python got it a bit right here.
If the import resolver goes from relative to root in its search, it is going to result in unwanted behavior where you actually want it to choose a module from root rather than relatively.
This will inadvertently lead to having to rename modules, just to get import resolver to work right. I'd rather be explicit here.
No description provided.