Skip to content

Instantly share code, notes, and snippets.

@StringEpsilon
Last active December 6, 2022 00:18
Show Gist options
  • Save StringEpsilon/c8a5df1bb8db85a93414b2cbb0a208ce to your computer and use it in GitHub Desktop.
Save StringEpsilon/c8a5df1bb8db85a93414b2cbb0a208ce to your computer and use it in GitHub Desktop.
Syntaxia - a rust insprired programming language designed by ChatGPT

Keywords

The following keywords are reserved in syntaxia and cannot be used as identifiers (variable names, function names, etc.) without special syntax:

  • let: Declares a variable or pattern.
  • var: Declares a variable or pattern that can be assigned to multiple times.
  • realize: Implements a trait or generic type.
  • trait: Defines a trait.
  • function: Declares a function or method.
  • returns: Specifies the return type of a function or method.
  • borrowed: Indicates that a variable or argument is a borrowed reference.
  • mutable: Indicates that a variable is mutable.
  • enum: Defines an enumeration.
  • struct: Defines a structure.

Operators

Syntaxia supports the following operators:

  • Arithmetic: +, -, *, /, %, +=, -=, *=, /=, %=
  • Comparison: ==, !=, >, <, >=, <=
  • Logical: &&, ||, !
  • Bitwise: &, |, ^, ~, <<, >>, &=, |=, ^=, <<=, >>=
  • Miscellaneous: .., to

Control Flow

Syntaxia supports the following control flow constructs:

  • if: Executes a block of code if a condition is true.
  • else: Executes a block of code if the if condition is false.
  • while: Repeatedly executes a block of code while a condition is true.
  • for: Repeatedly executes a block of code for each element in a range or iterator.
  • match: Matches a value against a pattern and executes a block of code for each matching case.

Note that in syntaxia, the if and while statements require the use of brackets {} around the code block, even when the code block contains only one statement.

Data Types

Syntaxia includes the following built-in data types:

  • boolean: Represents a boolean value of true or false.
  • integer: Represents a signed 32-bit integer value.
  • long: Represents an unsigned 64-bit integer value.
  • double: Represents a double-precision 64-bit floating-point value.

In addition to the built-in types, syntaxia also allows the declaration of custom data types using the enum and struct keywords.

Syntaxia also includes a optional type, which is used to represent values that may or may not be present.

Functions and Methods

In syntaxia, functions and methods are declared using the function keyword, followed by the function name, a list of parameters enclosed in parentheses (), and the return type after the returns keyword.

Here is an example of a function declaration in syntaxia:

function add(x: integer, y: integer) returns integer {
    return x + y;
}

In this example, the add function takes two integer parameters, x and y, and returns an integer value.

Methods are declared similarly to functions, but with the self keyword as the first parameter to indicate that the method is being called on an instance of the parent struct or trait.

Here is an example of a method declaration in syntaxia:

struct Point {
    x: integer,
    y: integer,
}

impl Point {
    function distance_from_origin(self) returns integer {
        let dx = self.x.abs();
        let dy = self.y.abs();
        return (dx * dx + dy * dy).sqrt();
    }
}

In this example, the Point struct has a method named distance_from_origin that calculates the distance of the Point instance from the origin and returns the result as an integer value.

Structs and Enums

In syntaxia, custom data types can be declared using the struct and enum keywords.

A struct is a composite data type that groups together zero or more values with different types. Each value in a struct is called a field and has a name associated with it.

Here is an example of a struct declaration in syntaxia:

struct Point { x: integer, y: integer, }

In this example, the Point struct has two fields, x and y, of type integer.

An enum is a data type that represents a set of discrete values. Each value in an enum is called a variant.

Here is an example of an enum declaration in syntaxia:

enum Color { Red, Green, Blue, }

In this example, the Color enum has three variants, Red, Green, and Blue.

To use a struct or enum, it must first be declared, and then it can be instantiated by creating an instance of the struct or enum and assigning values to its fields or variants.

Here is an example of using a struct and enum in syntaxia:

// Declare the Point struct struct Point { x: integer, y: integer, }

// Declare the Color enum enum Color { Red, Green, Blue, }

// Create an instance of the Point struct and initialize its fields let origin = Point { x: 0, y: 0 };

// Create an instance of the Color enum and assign a variant value let color = Color::Red;

In this example, the Point struct is declared and then an instance of the struct named origin is created and initialized with values for the x and y fields. The Color enum is declared and then an instance of the enum named color is created and assigned the Red variant value.

Macros and Attributes

In syntaxia, macros are a way to define custom code transformations that can be applied at compile time. Macros are declared using the macro keyword, followed by the macro name and the macro body enclosed in braces {}.

Here is an example of a macro declaration in syntaxia:

macro my_macro { // Macro body goes here }

In this example, the my_macro macro is declared and can be used to perform custom code transformations at compile time.

To apply a macro, the macro name is followed by the ! suffix.

Here is an example of using a macro in syntaxia:

// Declare the my_macro macro macro my_macro { // Macro body goes here }

// Use the my_macro macro my_macro!();

In this example, the my_macro macro is declared and then applied using the ! suffix.

Attributes in syntaxia are used to attach metadata to declarations, such as functions, structs, enums, and modules. Attributes are denoted using the #[attribute_name] syntax.

Here is an example of using attributes in syntaxia:

// Attach the deprecated attribute to the my_function function #[deprecated] function my_function() { // Function body goes here }

// Attach the serde attribute to the MyStruct struct #[serde] struct MyStruct { // Struct fields go here }

In this example, the deprecated attribute is attached to the my_function function, and the serde attribute is attached to the MyStruct struct. These attributes can be used by the compiler or other tools to provide additional information or behavior related to the declarations they are attached to.

Inbuilt Types and Standard Library

syntaxia has a number of inbuilt types, such as integers, floating-point numbers, booleans, and strings, that can be used to represent data in programs.

Here is a list of some of the inbuilt types in syntaxia and their corresponding types in rust:

  • integer: i32
  • long: u64
  • double: f64
  • boolean: bool
  • optional: Option
  • string: String

In addition to the inbuilt types, syntaxia also has a standard library that provides a set of common functions and data structures that can be used in programs. The standard library is imported using the use keyword, followed by the module name, and is accessed using the std:: prefix.

Here is an example of using the standard library in syntaxia:

// Import the std module use std;

// Create a vector of integers let numbers = std::vec![1, 2, 3, 4, 5];

// Iterate over the vector for number in numbers { // Print the number std::println(number); }

In this example, the std module is imported and then used to create a vec of integers, which is a dynamically-sized array data structure provided by the standard library. The vec is then iterated over using a for loop, and each element is printed using the println function from the std module.

Error Handling

In syntaxia, errors are represented using the Result type from the standard library. The Result type can either be an Ok variant, which indicates that the operation was successful, or an Err variant, which indicates that the operation failed.

Here is an example of using the Result type in syntaxia:

// Import the std module use std;

// Define the divide function function divide(x: integer, y: integer) returns Result<integer, String> { if y == 0 { return std::Err("Division by zero".to_string()); } else { return std::Ok(x / y); } }

// Call the divide function let result = divide(10, 0);

// Handle the result match result { std::Ok(value) => std::println(value), std::Err(error) => std::println(error), }

In this example, the divide function is defined to take two integers and return a Result type with an integer value in the Ok variant and a String value in the Err variant. The divide function checks if the divisor is zero, and if it is, it returns an Err variant containing an error message. Otherwise, it returns an Ok variant containing the result of the division.

The divide function is then called, passing in the numerator and divisor as arguments. The result variable is assigned the return value of the divide function.

The result variable is then matched using a match expression, which allows different actions to be taken based on the variant of the Result type. In this case, if the Ok variant is matched, the value is printed using the println function from the std module. If the Err variant is matched, the error message is printed using the println function.

Ownership and Borrowing

syntaxia uses a borrowing and ownership system to manage the allocation and deallocation of memory in programs. This system ensures that memory is automatically freed when it is no longer needed, preventing common errors such as dangling pointers and memory leaks.

The ownership system in syntaxia is based on the concept of a variable owning the data it holds. When a variable goes out of scope, the data it holds is automatically deallocated.

Here is an example of the ownership system in syntaxia:

// Define the my_string function function my_string() -> String { // Create a string let s = "Hello".to_string();

// Return the string
s

}

// Call the my_string function let s1 = my_string();

// Print the string std::println(s1);

// Call the my_string function again let s2 = my_string();

// Print the string std::println(s2);

In this example, the my_string function is defined to return a String. The my_string function creates a String value and assigns it to the s variable. The s variable then goes out of scope when the my_string function returns, and the String value is automatically deallocated.

The my_string function is then called twice, and the return value is assigned to the s1 and s2 variables. The s1 and s2 variables are then used to print the String values.

The ownership system in syntaxia ensures that the String values are automatically deallocated when they are no longer needed, preventing memory leaks.

The borrowing system in syntaxia is used to allow multiple variables to reference the same data without causing ownership issues. This is accomplished using borrowed references, denoted using the & sigil.

Here is an example of the borrowing system in syntaxia:

// Define the add_one function function add_one(x: &integer) -> integer { // Increment the value of x by 1 x + 1 }

// Create an integer variable let x = 1;

// Borrow a reference to x let y = &x;

// Call the add_one function let z = add_one(y);

// Print the value of z std::println(z);

In this example, the add_one function is defined to take a borrowed reference to an integer and return an integer. The add_one function increments the value of the integer by 1 and then returns the result.

The x variable is then created and initialized with the value 1. The y variable is then created and assigned a borrowed reference to the x variable using the & sigil.

The add_one function is then called, passing in the borrowed reference to x held by the y variable. The return value of the add_one function is then assigned to the z variable.

The z variable is then used to print the value of the integer, which should be 2.

The borrowing system in syntaxia allows multiple variables to reference the same data without causing ownership issues. This is important for allowing data to be shared between functions and other parts of the program without causing problems.

Structs and Enums

syntaxia provides two types of user-defined types: structs and enums.

Structs are used to define complex data types that can hold multiple values of different types. Structs are similar to classes in other programming languages, but they do not support inheritance or polymorphism.

Here is an example of a struct in syntaxia:

// Define the Point struct struct Point { x: integer, y: integer, }

// Define the main function function main() { // Create a new Point struct let p = Point { x: 1, y: 2 };

// Print the values of the Point struct
std::println(p.x);
std::println(p.y);

}

In this example, the Point struct is defined to have two fields: x and y. Both fields are of the integer type.

The main function is then defined, which creates a new Point struct and assigns it to the p variable. The Point struct is initialized with the values 1 and 2 for the x and y fields, respectively.

The p variable is then used to print the values of the Point struct. The output should be the values 1 and 2, corresponding to the values of the x and y fields in the Point struct.

Structs in syntaxia can also have methods, which are functions associated with a struct. Methods can access and modify the fields of the struct, and they can also be called on instances of the struct.

Here is an example of a struct with a method in syntaxia:

// Define the Point struct struct Point { x: integer, y: integer, }

// Define the distance method for the Point struct realize Point { function distance(self, other: &Point) -> double { // Calculate the distance between the two points let dx = other.x - self.x; let dy = other.y - self.y; (dx * dx + dy * dy).sqrt() } }

// Define the main function function main() { // Create two new Point structs let p1 = Point { x: 1, y: 2 }; let p2 = Point { x: 4, y: 6 };

// Print the distance between the two points
std::println(p1.distance(&p2));

}

In this example, the Point struct is defined as before. The distance method is then defined for the Point struct inside a realize block. The distance method takes another Point struct as an argument and returns a double value representing the distance between the two points.

The main function is then defined, which creates two Point structs and assigns them to the p1 and p2 variables. The p1 and p2 variables are then used to call the distance method on the p1 struct, passing in the p2 struct as an argument. The return value of the distance method is then printed to the console.

Enums in syntaxia are used to define a type with a fixed set of values. Each value of an enum is called a variant, and each variant can have associated data. Enums are useful for representing a small number of possible values that a variable can take on.

Here is an example of an enum in syntaxia:

// Define the Color enum enum Color { Red, Green, Blue, }

// Define the main function function main() { // Create a Color variable let c = Color::Red;

// Match the Color variable
match c {
    Color::Red => std::println("Red"),
    Color::Green => std::println("Green"),
    Color::Blue => std::println("Blue"),
}

}

In this example, the Color enum is defined with three variants: Red, Green, and Blue. The main function is then defined, which creates a Color variable and assigns it the Red variant.

The match expression is then used to match on the value of the c variable. If the value of the c variable is Red, the std::println function is called with the string "Red". If the value of the c variable is Green, the std::println function is called with the string "Green". If the value of the c variable is Blue, the std::println function is called with the string "Blue".

Enums in syntaxia can also have associated data for each variant. This allows the variants to hold different values of different types. Here is an example of an enum with associated data:

// Define the Shape enum enum Shape { Circle { radius: double, }, Rectangle { width: double, height: double, }, }

// Define the main function function main() { // Create a Shape variable let s = Shape::Circle { radius: 2.0 };

// Match the Shape variable
match s {
    Shape::Circle { radius } => std::println(radius),
    Shape::Rectangle { width, height } => std::println(width * height),
}

}

In this example, the Shape enum is defined with two variants: Circle and Rectangle. The Circle variant has associated data of type double representing the radius of the circle. The Rectangle variant has associated data of type double representing the width and height of the rectangle.

The main function is then defined, which creates a Shape variable and assigns it the Circle variant with a radius of 2.0.

The match expression is then used to match on the value of the s variable. If the value of the s variable is a Circle, the radius of the circle is printed to the console. If the value of the s variable is a Rectangle, the area of the rectangle is printed to the console.

Macros in syntaxia are used to generate code at compile time. Macros are defined with the macro keyword and can take arguments. Macros are called with the ! suffix and can be passed arguments in the same way as functions.

Here is an example of a macro in syntaxia:

// Define the min macro macro min(x: T, y: T) -> T where T: PartialOrd { if x < y { x } else { y } }

// Define the main function function main() { // Call the min macro with two arguments let z = min!(1, 2);

// Print the result
std::println(z);

}

In this example, the min macro is defined to take two arguments of type T and return a value of type T. The min macro uses the if expression to compare the two arguments and returns the smaller of the two values.

The main function is then defined, which calls the min macro with the arguments 1 and 2. The return value of the min macro is then assigned to the z variable and printed to the console.

Attributes in syntaxia are used to attach metadata to various parts of the code, such as functions, structs, enums, and more. Attributes are defined with the #[attribute_name] syntax and can take arguments.

Here is an example of an attribute in syntaxia:

// Attach the #[test] attribute to the main function #[test] function main() { // Test code goes here }

In this example, the #[test] attribute is attached to the main function. This attribute can be used by a testing framework to run the main function as a test.

Modules in syntaxia are used to organize code into logical units and allow for better organization and modularization of code. This helps to prevent naming collisions and improve the overall readability and maintainability of the code.

// Define a module named my_module module my_module { // Define a function named my_function in the my_module module function my_function() { // Function code goes here } }

// Define the main function function main() { // Call the my_function function in the my_module module my_module::my_function(); }

In this example, the my_module module is defined with a function named my_function. The main function is then defined, which calls the my_function function in the my_module module.

The crate keyword in syntaxia is used to specify the root module of a crate. A crate is a binary or library in the syntaxia ecosystem.

Here is an example of the crate keyword in syntaxia:

// Define the root module of the crate crate my_crate { // Crate code goes here }

In this example, the my_crate crate is defined as the root module of the crate. All code within the my_crate module will be part of the crate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment