AdeptLanguage / Adept

The Adept Programming Language
GNU General Public License v3.0
122 stars 9 forks source link

Few questions. #4

Open ghost opened 4 years ago

ghost commented 4 years ago

@IsaacShelton

  1. When you'll release documentation for Adept? (I really want to learn Adept.)

  2. Is there any way to read command-line arguments like in C?

int main(int argc, char *argv[]) { ... }
IsaacShelton commented 4 years ago
  1. Yes. There is a full language spec PDF under releases which may interest you, although it is pretty high level at explaining stuff. More documentation will be coming soon and if you have any questions, I'm always here to answer them in the meantime.

  2. Yes. In Adept, the full main function is: func main(argc int, argc **ubyte) int {}

Which is the same definition, just with slightly different syntax of course.

Since you seem to have some knowledge of C, if you like an easy start, you can use functions straight out of C, like malloc, free, printf, fgets, etc. from 'sys/cstdio.adept', 'sys/cstdlib.adept', respectively.

Let me know if/when you have questions!

ghost commented 4 years ago

@IsaacShelton What means in, out and inout Adept keywords?

cstdlib.adept:

foreign getenv(in *ubyte) *ubyte
IsaacShelton commented 4 years ago

in, out, and inout are annotations to help the programmer understand the data flow of a function.

func getMessage(out message **ubyte) successful { /* ... */ }

is equivalent to

func getMessage(message **ubyte) successful { /* ... */ }

The reason why you might want them in some cases, is when pointer parameters are taken in and it is unclear how the function uses them. For example:

func myFunction(a_pointer_value **int) successful { /* */ }

If we are looking to call myFunction, it doesn't say how it uses the pointer we supply it, it could be wanting to read values, or it could be looking to write values, or both.

func myFunction(in a_pointer_value **int) successful { /* */ }

Since in is now present, we know that myFunction only intends to read from the pointer and not write to it. So we can now know how to properly supply it with the kind of data it's looking for.

ghost commented 4 years ago

@IsaacShelton I understood. Also I have a question about standart library.

  1. Whats difference between 2.1/, 2.2/ and 2.3/ libraries, which one is the main? Can I remove 2.1/ or it is necessary for Adept?

  2. Why this happens?

error:

file.adept:4:12: Can't create string literal without String type present!
4|    printf("%d\n", 0)
             ^^^^^^

file.adept:

import "sys/cstdio.adept"

func main(argc int, argc **ubyte) int {
        printf("%d\n", 0)
}

In C printf("%d\n", 0); works correctly.

IsaacShelton commented 4 years ago
  1. 2.1, 2.2, and 2.3 are different versions of the standard library (that correspond to the respective Adept versions that they were made for. It's best to use the latest that your compiler supports, which should be 2.3.

Although you could remove 2.1, I won't recommend it, because if you ever want to compile programs that depend on that specific version the standard library, they won't be able to be compiled. It's only a few kilobytes anyway so you won't really gain much storage from it.

  1. There are two types of strings in Adept. There are C style null-terminated strings and there are ownership based Adept style strings.

C-style null terminated strings use single quotes, they are of type *ubyte which is equivilent to char* in C. printf('%d\n', 0)

Adept-style strings use double quotes, and require that you import '2.x/String.adept' to use them. When they run out of scope, they will deallocate memory if they have ownership. They are not null-terminated. The structure of a String is:

struct String (array *ubyte, length usize, capacity usize, ownership StringOwnership)

Ownership can be passed from one Adept-style string to another using to = from.commit(). It is important to remember to transfer ownership when strings are returned or run out of scope, because otherwise the contents of the string will be deleted and the return value will reference deallocated memory.

Adept-style strings can be converted to C-style strings using .cstr() which will return an allocated C-string. C-style strings can be converted to Adept-style strings using stringConstant(...) or string(...). The difference is that string(...) will create a copy of the bytes and then have ownership.

The benefit of having Adept-style strings the way they are, is that memory allocation/deallocation is minimized and the String type also works as a StringView type.

Lists in Adept also have the same ownership based model as Strings. Also a random side note: Character literals are defined using 'A'ub instead of 'A' like in C.

Hope this helps!

ghost commented 4 years ago

@IsaacShelton What means pragma compiler_supports '2.2'? Should I use it with 2.2/ standart library or I can ignore it? Is this similar to gcc -std=<version>?

IsaacShelton commented 4 years ago

Yeah it is similar to gcc -std=<version>. All it does is tell the compiler that it expects features from Adept 2.2 to be available. You can ignore it.

ghost commented 4 years ago

@IsaacShelton Why don’t you add the -std option? I thinks that this will be better than just importing from <version>/.

IsaacShelton commented 4 years ago

@t0md3an I hadn't thought of doing that... That's a good idea. My one concern is that the version of the stdlib that the program intends on using might not be obvious from the source code, which could lead to confusion on how to compile programs. One of the big things that I want to keep is the ability to compile any adept program by just running adept. This could be fixed though with something like pragma default_std '2.3'.

I think not having to put 2.3/ for every standard import would be good, but I want to retain the ability to compile programs without having to guess what version of the stdlib they use. Because unlike the C stdlib, sometimes thinks get changed/moved/deleted and if I want to keep improving the language while still maintaining backwards-conpatibility, it's important to have the ability to drastically change the standard library.

If I was to implement this, there are three main syntax possibilities: (fake folder, but retains file like importing)

pragma default_std '2.3'
import "std/basics.adept"

func main {
    print("Hello World")
}

or (cleanest and easiest to type, least intuitive)

pragma default_std '2.3'
import basics

func main {
    print("Hello World")
}

or no special syntax (most flexible, hard to type)

#set __std__ '2.3'
#import __std__ + '/basics.adept'

func main {
    print("Hello World")
}

(default_std could be named something else, I haven't considered different names for it yet)

pragma compiler_version/pragma compiler_supports could also be used to infer the default stdlib version

If I do add this, I'm currently leaning towards the middle one. I would probably also add a warning if pragma default_std is used but not specified, in order to encourage specifying which version is expected.

Although adding just -std=2.3 looks good in the short term, it makes it harder to compile old programs written with it. Unless you use a build script like Makefile, which I want to avoid.

What do you think?

ghost commented 4 years ago

@IsaacShelton

If you are focusing on the code and not on how to build it, then it is better to use default_std, but in my opinion -std would be a good option to start with (plus it will not need to be used when specifying the default (current 2.3) version, only for rollback (2.1, 2.2))

rollback to stdlib v2.2:

adept main.adept -o a.out -std="2.2"

use default stdlib:

adept main.adept -o a.out

IsaacShelton commented 4 years ago

@t0md3an Yeah I think that would well. The idea behind default_std is that you can still run just adept to compile it, but if you want to force a different stdlib version, you can use -std="2.1" or whatever.

adept which will use the specified default_std (or the latest version if not specified)

vs.

adept -std=2.1 which ignore any specified default_std, and use a specific version

I'm liking this more now, I'm gonna implement it and see how it feels to use

IsaacShelton commented 4 years ago

@t0md3an I've implemented it, and I really like it so far.

import basics

func main {
    print("Hello World!")
}

compiled with adept -std=2.3.

Or there is also the corresponding pragma directive:

pragma default_stdlib '2.3'
import basics

func main {
    print("Hello World!")
}

compiled with adept

You can pull it down and try it out if you like. Just be careful to not overwrite your Makefile.

ghost commented 4 years ago

@IsaacShelton

Thanks for adding the -std and default_stdlib, it makes it easier to choose the std version.

Also are you planning to add sysroot for import? I mean something like /usr/include in C.

IsaacShelton commented 4 years ago

@t0md3an

A sysroot for import already exists, it's just the folder named 'import' wherever the adept executable is located. Everything in that folder you can import from anywhere just like /usr/include. Depending on where you put the adept executable, the folder should be at /Library/Adept/2.x.0/import or ~/Library/Adept/2.x.0/import. If you have the latest build from today, you should be able to see your import folder location and your stdlib folder location by doing adept --version.

Mine looks something like this:

Platform:       MacOS

Adept Build:    Adept 2.4 - Build Aug 30 2020 11:55:26 CDT
Adept Version:  2.4
Pkg Manager:    disabled

Import Folder:  "/Users/isaac/Projects/Adept/bin/import/"
Stblib Folder:  "/Users/isaac/Projects/Adept/bin/import/2.4"
ghost commented 4 years ago

@IsaacShelton

Importing from import/2.4 - import module.adept?

Importing from import/ - import "module.adept"?

And how to import local file?

IsaacShelton commented 4 years ago

@t0md3an To import a local file you can do

import "my_file.adept"

The way that Adept finds which file you're referring to, is by first looking in the same directory as the source file, and then looking in import/ if it couldn't find it locally.

When using the name of a standard library component

import String

Adept will only look for it at {stdlib}/{name}.adept (so import/2.4/String.adept in this example)

ghost commented 4 years ago

@IsaacShelton

As I understood:

Does it work like that?

I thought they were the same.

Is it possible to allow " in cstdio printf()?

IsaacShelton commented 4 years ago
  1. You're right with ', but not with "

' - *ubyte which is like char* or char[] in C " - String which is kinda like std::string_view in C++. Expect it also has fancy stuff going on behind the scenes.

When using string values (' or ") for things like pragma directives, meta directives, imports, and such, 'string' and "string" are treated the same.

import basics

import "my_file.adept" // is the same as:
import 'my_file.adept'

but inside the actual code, they are different.

func main {
    name *ubyte = 'This is a null-terminated string'
    // is different from
    name String = "This is not a null-terminated string"
}
  1. It's not really possible to allow " in printf with what's in the language right now. If more support for varargs is added sometime, then an implementation of printf that takes a String could be made in the future.

Something like:

func printf(format String, ...) int {
    // TODO: Write printf that uses the String type
}

As weird as it feels to use ' for C-strings, you get used to it pretty fast.

printf('Hello World!\n')

And " has its own way of doing printf like stuff, by using %.

import basics

func main {
    value int = 10
    print("Value is %" % value)
    firstname String = "John"
    lastname String = "Smith"
    fullname String = "% %" % firstname % lastname
}

If you hate ', then I would recommend just learning normal " strings.

ghost commented 4 years ago

@IsaacShelton

import lib/lib

If lib/lib placed in 2.x returns an error:

Expected type!

Works only if:

import '2.4/lib/lib'
IsaacShelton commented 4 years ago

@t0md3an

Nice catch!

This is fixed now.

import lib1/lib2
import lib1/lib2/lib3

If you want autocompletion in AdeptIDE to still work properly, you'll need to rebuild it after doing make deepclean.

ghost commented 4 years ago

@IsaacShelton

1. Does Adept support Linux?

2. Can you give me example of using vectors in Adept?

3. How to use pointers in Adept?

IsaacShelton commented 4 years ago
  1. Yes, but the precompiled libraries such as GLFW/OpenGL/OpenAL/ALUT/stb_image/etc. that come with AdeptImport don't work out of the box.

  2. By vectors, do you mean something like std::vector or something like Vector3f

  3. I'm going to assume what you already know what pointers are, if you don't already know what pointers are I would recommend first watching [https://www.youtube.com/watch?v=iChalAKXffs]()

Pointers in Adept are mostly identical to those in C/C++.

Pointer types are written with an * in front of the type that the pointer points to.

x *int   // Adept

as opposed to in C, where * is put afterwards

int* x;  // In C/C++

The "address of" & and "dereference/value of" * operators are the same as in C/C++

import 'sys/cstdio.adept'
import 'sys/cstdlib.adept'

func main {
    x int = 5
    y *int = &x
    *y = 10
    printf('x = %d\n', x)
}

The one difference though is that when adding/subtracting from pointers in Adept, they do not take into account the size of the type they are pointing at:

// Adept
x *int = null
printf('%p\n', x + cast ptr 1)

// This will have printed '0x00000001'
// C/C++
int *x = NULL;
printf("%p\n", x + 1)

// This will have printed '0x00000004' on most systems

Also like in C/C++, pointers to blocks of memory can serve as arrays and can have the [] operator used on them:

some_integers = malloc(10 * sizeof int)
defer free(some_integers)

// Set each element to be the same as its index in the array
repeat 10, some_integers[idx] = idx

// Print the 4th element, which should be 3
printf('%d\n', some_integers[3])
ghost commented 4 years ago

@IsaacShelton Thanks. I mean something like std::vector in Adept.

IsaacShelton commented 4 years ago

@t0md3an Adept has List which serves the same purpose as an std::vector in C++.

import basics

func main {
    // Declare a List that holds 'int's
    my_integers <int> List

    // Adds numbers 0 through 9 inclusive to the list
    repeat 10, my_integers.add(idx)

    // Print each integer in the list
    each int in my_integers, printf('%d\n', it)
}

Similar to the String type in Adept, Lists are destroyed when they go out of scope.

import basics

func main {
    // Obtain ownership of a list that will be given to us by 'getMyListOfIntegers()'
    my_integers <int> List = getMyListOfIntegers()

    // Print each int the the list
    each int in my_integers, printf('%d\n', it)

    // (my_integers has ownership, so the list is destroyed)
}

func getMyListOfIntegers() <int> List {
    list <int> List
    list.add(1234)
    list.add(314159)

    // We want to return 'list', but list will be destroyed by the time
    // we pick back up at the 'my_integers <int> List = ...' line in main,
    // because it is going to run out of scope

    // To fix this, we will transfer the ownership of 'list' to a temporary list which will
    // give ownership to 'my_integers' when the function is called. We do this
    // using commit()
    // NOTE: If this isn't done, the program will crash
    return list.commit()
}

In order to get certain elements of a list, you can use the get(usize) and getPointer(usize) methods:

func main {
    // Get list of integers
    my_list <int> List = getMyListOfIntegers()

    // Print the first element
    printf('%d\n', my_list.get(0))

    // Get a pointer to the second element and change it
    my_pointer *int = my_list.getPointer(1)
    *my_pointer = 123456789

    // Print the second element, which will be 123456789 since we changed it through our pointer
    printf('%d\n', my_list.get(1))
}

By default, .get and .getPointer do bounds checking, if this is not desired, you can disable it by putting

#set list_bounds_checks false

and at the beginning of your file

ghost commented 4 years ago

@IsaacShelton I understood.

Why Adept uses import instead of #include, (as I know Adept has preprocessor, but does not has #include)?

IsaacShelton commented 4 years ago

@t0md3an

Adept uses import instead of #include because it eliminates the need for headers guards.

In C you have to do something like this:

#ifndef _MYFILE_H
#define _MYFILE_H
// ... definitions ...
#endif

In order to prevent the file from being "included" more than once.

Adept only lets you "include" a file once when you use import or #import, so you can skip the header guard

// ... definitions ...

So if you import a file more than one time, you only end up with one copy of the definitions

ghost commented 4 years ago

@IsaacShelton

Maybe I found a bug.

Code:

#import basics

func main()
{
    print("This is my first program.")
}

Result:

file.adept:1:9: Warning: Usage of undefined transcendant variable 'basics'
    (you can disable this warning with '--unsafe-meta' or 'pragma unsafe_meta')
file.adept:1:1: The file 'undef' doesn't exist!
1|#import basics
  ^^^^^^^
IsaacShelton commented 4 years ago

@t0md3an

This is because #import treats basics as a preprocessor variable.

The warning

file.adept:1:9: Warning: Usage of undefined transcendant variable 'basics'

is complaining that the preprocessor variable 'basics' is undefined. And when #import then uses the contents of the preprocessor variable 'basics', it says:

file.adept:1:1: The file 'undef' doesn't exist!

Because the preprocessor variable that it got the value from, is undefined.

When using #import, it's usually to do something like:

#default my_version_number 3
#default my_library_location "~/my_library/src/"

#import my_library_location + "my_library" + my_version_number + ".adept"

which in this example, boils down to

import "~/my_library/src/my_library3.adept"

So yeah in this case, it's not a bug, it's just that #import works different from import and doesn't support importing like that

IsaacShelton commented 4 years ago

@t0md3an The keywords[] array has to be sorted alphabetically for it to work. And the TOKEN_* values from BEGINNING_OF_KEYWORD_TOKENS to MAX_LEX_TOKEN in token.h need to be sorted to match the alphabetical order of those in keywords[]

Also if you care about debugging tokenlists, you have to reorder global_token_name_table[] so that the token ids match the name of the tokens

IsaacShelton commented 4 years ago

In token.h you need to replace

#define BEGINNING_OF_KEYWORD_TOKENS TOKEN_POD
#define TOKEN_POD              0x00000050
#define TOKEN_ALIAS            0x00000051
#define TOKEN_AND              0x00000052
#define TOKEN_AS               0x00000053
#define TOKEN_AT               0x00000054
#define TOKEN_BREAK            0x00000055
#define TOKEN_CASE             0x00000056
#define TOKEN_CAST             0x00000057
#define TOKEN_CONTINUE         0x00000058
#define TOKEN_DEF              0x00000059
#define TOKEN_DEFAULT          0x0000005A
#define TOKEN_DEFER            0x0000005B
#define TOKEN_DELETE           0x0000005C
#define TOKEN_EACH             0x0000005D
#define TOKEN_ELSE             0x0000005E
#define TOKEN_ENUM             0x0000005F
#define TOKEN_EXTERNAL         0x00000060
#define TOKEN_FALLTHROUGH      0x00000061
#define TOKEN_FALSE            0x00000062
#define TOKEN_FOR              0x00000063
#define TOKEN_FOREIGN          0x00000064
#define TOKEN_FUNC             0x00000065
#define TOKEN_FUNCPTR          0x00000066

with

#define BEGINNING_OF_KEYWORD_TOKENS TOKEN_POD
#define TOKEN_POD              0x00000050
#define TOKEN_FUNC             0x00000051
#define TOKEN_ALIAS            0x00000052
#define TOKEN_AND              0x00000053
#define TOKEN_AS               0x00000054
#define TOKEN_AT               0x00000055
#define TOKEN_BREAK            0x00000056
#define TOKEN_CASE             0x00000057
#define TOKEN_CAST             0x00000058
#define TOKEN_CONTINUE         0x00000059
#define TOKEN_DEF              0x0000005A
#define TOKEN_DEFAULT          0x0000005B
#define TOKEN_DEFER            0x0000005C
#define TOKEN_DELETE           0x0000005D
#define TOKEN_EACH             0x0000005E
#define TOKEN_ELSE             0x0000005F
#define TOKEN_ENUM             0x00000060
#define TOKEN_EXTERNAL         0x00000061
#define TOKEN_FALLTHROUGH      0x00000062
#define TOKEN_FALSE            0x00000063
#define TOKEN_FOR              0x00000064
#define TOKEN_FOREIGN          0x00000065
#define TOKEN_FUNCPTR          0x00000066

Because BEGINNING_OF_KEYWORD_TOKENS + (index in keywords array) = TOKEN_FOR_THAT_KEYWORD

This is done this way, so that when you convert from an index in the keywords array to a token ID, it is of time O(1) instead of O(log(n)) or O(n).

The // 0x00000000 isn't necessary, it just helps to show me which token ID goes with which string.

ghost commented 4 years ago

@IsaacShelton Thanks, now it works

ghost commented 4 years ago

@IsaacShelton

  1. Is there any difference between #print_error and #place_error? Which one is similar to C/C++ #error?

  2. Why I can't sqrt integer sqrt(4)?

file.adept:7:11: Undeclared function sqrt(int)!
7|    print(sqrt(4))
            ^^^^

Potential Candidates:
    sqrt(double) double
  1. Why I can't print() integer? It is very uncomfortable.
file.adept:7:5: Undeclared function print(int)!
7|    print(0)
      ^^^^^

Potential Candidates:
    print(String) void

Please add more flexibility (I mean printing all types, adding strings to printf... etc) this will make programming in Adept more comfortable.

I can make a site for your language and promote it on reddit or Twitter. I mean that I want to help.

IsaacShelton commented 4 years ago
  1. #print_error also prints a newline, where as #place_error does not

The equivalent of #error "Something went wrong" is:

#print_error "Something went wrong!"
#halt
  1. Because it's just not a part of the standard library even though it probably should be. If you want to be able to sqrt any number, you can just define another function like this:

    func sqrt(number $T~__number__) $T = sqrt(number as double) as $T

    Also make sure that you import either math or "sys/cmath.adept" I'll take this into account and add this line into the standard library sometime soon

  2. Same reason as above, just not a part of the library yet even though it probably should be. In the mean time, you can add this which will allow you to print any number like that:

    
    // NOTE: Requires 'parse', which is already included by 'basics'

func print(number $T~number) { print(toString(number)) }



4. Yes I like all those ideas, and you are very welcome to help promote the language if you want
ghost commented 4 years ago

@IsaacShelton about 2,3 I have basics and cmath imported

IsaacShelton commented 4 years ago

@t0md3an I was looking around in basics and it turns out everything including parse, math, and 'sys/cmath.adept' is already imported from it directly or indirectly, so it actually doesn't matter if you imported them. It doesn't hurt though.

ghost commented 4 years ago

@IsaacShelton

Have you any plans about improving sys/ libraries (libc)? I mean add support for String or something.

IsaacShelton commented 4 years ago

@t0md3an I haven't thought about it, but it wouldn't be that hard to do. Maybe I'll add libraries like '2.4/cstdio.adept' which import 'sys/cstdio.adept' and add extra wrapper functions.

ghost commented 4 years ago

@IsaacShelton

How to read user input?

C++:

#include <iostream>

int main(void)
{
    int num;
    cin >> num;
}
IsaacShelton commented 4 years ago

@t0md3an You can read user input with the scan* functions from terminal. They definitions are:

func scan String { ... }
func scan(prompt String) String { ... }

func scanInt int { ... }
func scanInt(prompt String) int { ... }
func scanIntInto(out result *int) successful { ... }
func scanIntInto(prompt String, out result *int) successful { ... }

func scanDouble double { ... }
func scanDouble(prompt String) double { ... }
func scanDoubleInto(out result *double) successful { ... }
func scanDoubleInto(prompt String, out result *double) successful { ... }

An example using scan:

import basics

func main {
    name String = scan("What is your name? ")
    printf("Welcome %S!\n", name)

    age int = scanInt("How old are you? ")
    printf("Ok you are %d years old\n", age)

    answer double = scanDouble("What is 1 / 3? ")
    printf("You answered: %f\n", answer)
}
ghost commented 4 years ago

@IsaacShelton

Is there any input for all data types like cin (cin can scan all data types)?

IsaacShelton commented 4 years ago

@t0md3an Never thought to add one, but I think it's a good idea I'll add it

ghost commented 4 years ago

@IsaacShelton

Also can you give me full list of %<data type> for printf please.

IsaacShelton commented 4 years ago

@t0md3an There is now a version of scan which works how you described: You can use it like this

scan("Enter a value: ", def my_variable int)

Example using it:

import basics

func main {
    scan("What is your name? ", def name String)
    printf("Welcome %S!\n", name)

    scan("How old are you? ", def age int)
    printf("Ok you are %d years old\n", age)

    scan("What is 1 / 3? ", def answer double)
    printf("You answered: %f\n", answer)
}
IsaacShelton commented 4 years ago

@t0md3an Not all the types are directly supported yet, but as long as you don't disable runtime type information, you can use the following:

%d - works for bool, successful, byte, ubyte, short, ushort, int, uint, long, ulong, usize
%f - works for float, double
%s - works for *ubyte
%S - works for String
%p - works for ptr, *MyType
ghost commented 4 years ago

@IsaacShelton How to print **ubyte in printf?

IsaacShelton commented 4 years ago

@t0md3an If you want to print the pointer itself:

pointer **ubyte
printf("%p\n", pointer)

If you want to print the contents as an array:

each element *ubyte in [pointer, number_of_elements] {
    printf('%s\n', element)
}

If you want to print the contents as an array and don't need printf:

print(array(pointer, number_of_elements))

Or if you want to print contents as an array as an argument to printf

printf("Array contents: %S\n", toString(array(pointer, number_of_elements)))

For example:

import basics

func main(argc int, argv **ubyte) {
    printf("Got arguments: %S\n", toString(array(argv, argc)))
}

Or you can just use toString(*$T, usize) with the latest version of AdeptImport:

import basics

func main(argc int, argv **ubyte) {
    printf("Got arguments: %S\n", toString(argv, argc))
}
ghost commented 4 years ago

B@IsaacShelton What is this messages after executing binary file of Adept WARNING: <something> (#6, #8)? Is this is a part of Adept or this is LLVM system? Is this is a debugger?

IsaacShelton commented 4 years ago

@t0md3an What does the warning say?

ghost commented 4 years ago

@IsaacShelton I am just asking about the system of errors and warning in binary executable file, how this works and what program does this.

IsaacShelton commented 4 years ago

@t0md3an Most of the warnings that start with WARNING: are from Adept or files written in Adept. It's usually not related to LLVM unless it mentions it.