Reference manual and instruction guide for Carp
Version
1.0.6
for Carp1.0.5
This is a reference manual and instruction guide filled with samples and explanations learning Carp.
- Learning Carp
-
Ensure dotnet is installed
-
Run the following to install it from NuGet
$ dotnet tool install --global Carp
-
Check carp is installed
$ carp --version
-
It is recommended to use Visual Studio Code with the Carp Language extension to aide with development
Create a new file, give it the .carp extension, and write the following code:
import std.io
IO.println('Hello, world!')
Now run the file with the Carp interpreter:
$ carp hello.carp
You should see the output Hello, world!
printed to the console.
Alternatively you can go file-less and use the REPL:
$ carp
: import std.io
:
: IO.println('Hello, world!')
# Strings are only denoted with single quotes
str my_string = 'Hello, world!'
# Booleans are either true or false
bool my_bool = true
# Integers are whole numbers, decimals, and negative numbers
int my_int = 10
int my_decimal_int = -10.5
# Obj is the base type for all objects
obj my_obj = 5
# Null is the absence of a value
obj my_null = null
# Objects are automatically assigned null if not given a value
obj my_auto_null
# value is null
# If the object is a struct though, they will be auto-instatiated
int my_auto_int
# value is 0
# Let implicitly determines the type of the variable based on the value
let my_auto_typed_int = 5
# Collections are a list of objects, they are denoted with *
int* my_list = [1, 2, 3, 4, 5]
# Maps are a collection of key-value pairs
str:int my_map = [
'key1': 1,
'key2': 2,
'key3': 3
]
# Ranges are a collection between two values
range<int> my_range = 1..10
This is the base object of everything.
Strings are immutable, meaning they cannot be changed after they are created. They are represented by surrounding the text value with single quotes 'hello world'
. Unlike other languages, double quotes are not permitted. They can use backslashes to escape a quote character or another backslash.
Casting:
- From any type to
str
will perform override.string
on it int
Converts the string into an integerchr
Converts the string into a character, using the first character onlybool
Converts the string into a boolean, comparing it to 'true' and 'false'
Properties:
.length
int
Calculates the length of the string.lower
str
Converts the string to lowercase.upper
str
Converts the string to uppercase.clean
str
Removes leading and trailing whitespace.split(str delimiter)
str*
Splits the string into an array of strings based on a delimiter.replace(str source, str replacement)
str
Replaces all occurrences of a substring with another substring.contains(str search)
bool
Checks if the string contains a substring.startswith(str prefix)
bool
Checks if the string starts with a substring.endswith(str suffix)
bool
Checks if the string ends with a substring.encode(str encoding)
byte_sequence
Encodes a string to a byte array using the specified encoding. Encoding can be any of the following:utf8
,utf32
,unicode
,bigendianunicode
,latin
,ascii
Overrides:
iterate
String overrides the iterate method to allow enumeration of each character in the stringadd
String overrides the add method to concatenate two stringsmultiply
String overrides the multiply method to repeat the string a specified number of timesdivide
String overrides the divide method to split the string into a collection of strings based on a delimiter, which can be astr
or achr
index
String overrides the index method to get the character at a specified index, if a range is passed, it will return a substring based on that range
Characters are represented by using a backtick and then a single character \
a` .
Casting:
int
Converts the character into an integer
Properties:
.lower
chr
Converts the character to lowercase.upper
chr
Converts the character to uppercase.is_upper
bool
Checks if the character is uppercase.is_lower
bool
Checks if the character is lowercase.is_digit
bool
Checks if the character is a digit.is_alpha
bool
Checks if the character is a letter.is_symbol
bool
Checks if the character is a symbol.is_whitespace
bool
Checks if the character is whitespace.is_upper
bool
Checks if the character is uppercase.is_lower
bool
Checks if the character is lowercase
Integers are whole numbers, decimals, and negative numbers. They are represented by a number, and can be negative or decimal. They can be used in mathematical operations, and are represented by 5
, -5
, 5.5
. They are a struct
type which means they default to 0 instead of null.
Casting:
chr
Converts the integer into a character
Overrides:
add
Integer overrides the add method to add two integerssubtract
Integer overrides the subtract method to subtract two integersmultiply
Integer overrides the multiply method to multiply two integersdivide
Integer overrides the divide method to divide two integersmodulo
Integer overrides the modulo method to get the remainder of two integerspow
Integer overrides the power method to raise the integer to the power of another integergreater
Integer overrides the greater method to check if the integer is greater than another integerless
Integer overrides the less method to check if the integer is less than another integernegate
Integer overrides the negate method to get the inverse of the integerstep
Integer overrides the step method to get the current integer plus 1iterate
Iterator from 0 to the integer
Booleans are either true or false. They are represented by true
or false
. They can be used in logical operations, and are a struct
type which means they default to false
instead of null.
Overrides:
not
Boolean overrides the not method to get the inverse of the boolean
Functions are a type of object that can be called. They are represented by func<treturn>
where the generic type is the return type of the function. They can be called by using the ()
operator.
Overrides:
call
Function overrides the call method to execute the body of the function
Collections are a list of objects, they are represented by [obj0, obj1, obj2]
. They can be accessed by index, and can be iterated over. When a collection is not supplied with a type, it will default to the highest common type, if one exists, else obj*
Casting:
tvalue*
Converts the collection into a new collection, if it the item's type extends the sub_type
Properties:
.length
int
Calculates the length of the collection.first
tvalue
Gets the first item in the collection.last
tvalue
Gets the last item in the collection.append(tvalue item)
void
Appends an item to the end of the collection.remove(tvalue item)
void
Removes an item from the collection.removeat(int index)
void
Removes an item from the collection at a specified index.insert(int index, tvalue item)
void
Inserts an item into the collection at a specified index.contains(tvalue item)
bool
Checks if the collection contains an item.within(int index)
bool
Checks if the index is within the bounds of the collection.clear()
void
Clears the collection.get(int index, tvalue default)
tvalue
Gets the item at a specified index, if a range is passed, it will return a sub-collection based on that range
Overrides:
add
Collection overrides the add method allow merging two collections of the same typeiterate
Collection overrides the iterate method to allow enumeration of each item in the collectionindex
Collection overrides the index method to get the item at a specified index, if a range is passed, it will return a sub-collection based on that range
Maps are a collection of key-value pairs, they are represented by ['key0': value0, 'key1': value1]
. They can be accessed by key, and can be iterated over. It uses the same high common type rule as collections, if no type is supplied.
Properties:
.length
int
Calculates the length of the map.keys
tkey*
Gets all the keys in the map.values
tvalue*
Gets all the values in the map.remove(tkey key)
void
Removes an item from the map.contains(tkey key)
bool
Checks if the map contains a key.clear()
void
Clears the map.get(tkey key, tvalue default)
tvalue
Gets the value at a specified key, if the key is not found, it will return the default value
Ranges are represented as start..end
, they must be the same type and the object must override Less
, Subtract
and Step
. As a generic type, their type is referenced as range<tvalue>
Casting:
tvalue*
Converts range into a collection of type
Properties:
.length
int
Calculates length of range based onend
-start
.start
tvalue
Start of range.end
tvalue
End of range
Overrides:
iterate
Range overrides the iterate method to allow enumeration between the start and the end points
Types are used to store the type of an object, they are represented by t(obj)
. They can be used to check the type of an object, and can be used in casting.
Casting:
- From any type to
type
will return the type of the object
Properties:
.name
str
Gets the name of the type.base
type
Gets the base type of the type.is_struct
bool
Checks if the type is a struct
Byte sequences are a collection of bytes. They have no representation in the language, but can be used to store binary data. Any resources of an unknown type will default to a byte sequence.
Properties:
.length
int
Calculates the length of the byte sequence.decode(str encoding)
str
Decodes the byte sequence into a string of specified encoding type which can be any of the following:utf8
,utf32
,unicode
,bigendianunicode
,latin
,ascii
Overrides:
index
Byte Sequence overrides the index method to get the byte at a specified index, if a range is passed, it will return a sub-sequence based on that range. Note that the byte returned will be in the form of an integer, not a character.
Comments are used to annotate code, they are ignored by the interpreter.
# This is a comment
#(
This is a multi-line comment
)
Make sure a space is present after the #
symbol, otherwise the preprocessor will get confused with a directive.
Blocks are used to group statements together, there are two types of blocks in Carp: the shorthand block and the full block.
# Shorthand block
int add(int a, int b) -> a + b
# Full block
int add(int a, int b) {
return a + b
}
Casting is used to convert one type to another, and type checking is used to determine the type of an object.
# Object type coercion, this is implicit type conversion
int my_num = '5'
# value is 5
# Explicit type conversion
int my_num = '5' ~ int
# value is 5
# Type checking
bool is_int = my_num ~~ int
# value is true
# Type storage
type my_type = 'hi' ~ type
# value is str
Operators are used to perform operations on values, such as addition, subtraction, and comparison.
# Arithmetic operators
int add = 5 + 5
int subtract = 5 - 5
int multiply = 5 * 5
int divide = 5 / 5
int modulo = 5 % 5
int power = 5 ^ 5
# All of these operators can be combined with the assignment operator to modify the value of a variable
int num = 5
num += 5
# Comparison operators
bool equal = 5 == 5
bool not_equal = 5 != 5
# or alternatively
not_equal = 5 <> 5
bool greater_than = 5 > 5
bool less_than = 5 < 5
bool greater_than_or_equal = 5 >= 5
bool less_than_or_equal = 5 <= 5
# Logical operators
bool and = true & false
bool or = true | false
# Note that logical operators work on not only booleans, but also other objects,
# Where it will compare them with null (null being false and anything else being true)
# For example, null | 5 will return 5, as null is falsey
str value = null | 'hi'
# value is 'hi'
bool not = !true
bool inverse = -num
Control flow is used to determine the flow of the program, such as loops and conditionals.
# If statements
if 5 > 4 -> IO.println('5 is greater than 4')
# If-else statements
if 5 > 4 -> IO.println('5 is greater than 4')
else -> IO.println('5 is not greater than 4')
# While loops
int i = 0
while i < 5 {
IO.println(i)
i += 1
}
# For loops
for i : 0..5 {
IO.println(i)
}
# The 0..5 can actually be simplified to ..5
# But since for requires a collection, it will even take just 5
# For loops without storing the index
for 0..5 -> IO.println('hi')
Functions are used to encapsulate code into reusable blocks, they can take arguments and return values.
# The return type is specified first, or if none, void
int add(int a, int b) -> a + b
void print(str message) -> IO.println(message)
# The arguments and their types are specified in the parentheses
# The block is specified directly after the arguments
Classes are used to define objects with properties and methods, they can be instantiated and used in the program.
class MyClass {
# Properties are defined the same way as variables
int my_prop
# Methods are defined in the same way as functions
void print_prop() -> IO.println(this.my_prop)
# Constructors are used to initialize the object
# Also! You can have multiple constructors with different arguments
void init(int prop) -> this.my_prop = prop
}
# Instantiating a class
MyClass my_obj = MyClass.new(5)
my_obj.print_prop()
Structs are similar to classes, but they cannot be null, meaning they must have an empty constructor for implicit instantiation
struct Vec2 {
int x
int y
void init() {
this.x = 0
this.y = 0
}
void init(int x, int y) {
this.x = x
this.y = y
}
str string() -> 'Vec2(' + this.x + ', ' + this.y + ')'
}
Classes and structs can override special methods to change how operators and other operations work on them. The following methods can be overridden:
init
void
- Constructor, this is called when the object is created, you can have multiple constructors with different argumentsstring
str
- String representation of the object, casting to a string or usingIO.println
will call this methodproperty(str prop)
obj
- Gets a property of the object, this is called when using the.
operatorsetproperty(str prop, obj value)
obj
- Sets a property of the object, this is called when assigning to a.
propertycall(obj* args)
obj
- Calls the object as a function, this is called when using the()
operatorindex(obj index)
obj
- Gets an index of the object, this is called when using the[]
operatorsetindex(obj index, obj value)
obj
- Sets an index of the object, this is called when assigning to a[]
indexadd(obj value)
obj
- Adds a value to the object, this is called when using the+
operatorsubtract(obj value)
obj
- Subtracts a value from the object, this is called when using the-
operatormultiply(obj value)
obj
- Multiplies the object by a value, this is called when using the*
operatordivide(obj value)
obj
- Divides the object by a value, this is called when using the/
operatormodulus(obj value)
obj
- Gets the remainder of the object divided by a value, this is called when using the%
operatorless(obj value)
bool
- Checks if the object is less than a value, this is called when using the<
operatorgreater(obj value)
bool
- Checks if the object is greater than a value, this is called when using the>
operatorpow(obj value)
obj
- Raises the object to the power of a value, this is called when using the^
operatormatch(obj value)
bool
- Checks if the object matches a value, a default implementation exists for this, this is called when using the==
operatorstep()
obj
- Gets the next value of the object, this is used inrange
objects and can be used for iterationnegate()
obj
- Gets the inverse of the object, this is called when using the-
operatoriterate()
obj*
- Gets an iterator for the object, this is called when using thefor
loop or a winded expression
Winding and filtering allow applying an operation to a sequence of values. As of the current version, filtering is not supported, but uses the ;;
operator. Winding can be done using the ::
operator.
int nums = [1, 2, 3, 4, 5]
# this can actually be done using int* nums = 1..6
# or 1..6 ~ int* if type coercion is disabled
int new_nums = nums :: * 10
# value will be [10, 20, 30, 40, 50]
The winding operator ::
converts a collection into a "winded" object where all operations applied to it, will be applied to all items. This includes operators and properties, note that only one operation can be applied per wind.
str* names = ['John', 'Jane', 'Francis', 'Joe']
str* lower_names = names :: .lower
# value is ['john', 'jane', 'francis', 'joe']
As of the latest version, bool
operators cannot be used with winded expressions and produce a collection, instead they will produce a single value, based on whether they all matched true
.
Imports are used to include external code into the program, such as the standard library or other scripts.
They are controlled by the active package resolver. The std
prefix is available to access the standard library. The git
prefix is available to access the git package resolver. Not specifying one of these will search the current active directory or the current package's source.
# Standard library import
import std.io
# Github import
import git.username.repo
# Local import
import utils
The IO package is responsible for input and output operations in Carp. It contains functions for controlling the connected console.
Reference
void println(obj str)
Writes a line to the output.
void printw(obj str)
Writes a string to the output, without a newline.
str readln(obj str)
Reads a line from the input.
chr readw(bool hideKeyStrokes)
Reads a character from the input.
void clear()
Clears the output.
void move(int x, int y)
Move the cursor to the specified position.
Math is for extended mathematical operations, not included in the base language's operators. It also contains random number generation functions.
Reference
int random(int min, int max)
Generates a random integer within the specified range.
bool chance(int chance)
Determines if a random event occurs based on the specified chance.
void wait(int ms)
Pauses the execution of the current thread for the specified amount of time.
int abs(int num)
Returns the absolute value of the specified number.
int ceil(int num)
Rounds the specified number up to the nearest integer.
int floor(int num)
Rounds the specified number down to the nearest integer.
int round(int num)
Rounds the specified number to the nearest integer.
int max(int a, int b)
Returns the larger of two specified numbers.
int min(int a, int b)
Returns the smaller of two specified numbers.
int clamp(int num, int min, int max)
Clamps a number to a specified range.
int sqrt(int num)
Calculates the square root of a specified number.
int pow(int num, int pow)
Raises a specified number to the power of another specified number.
int sin(int num)
The sine of a specified number.
int cos(int num)
The cosine of a specified number.
int tan(int num)
The tangent of a specified number.
int asin(int num)
The arcsine of a specified number.
int acos(int num)
The arccosine of a specified number.
int atan(int num)
The arctangent of a specified number.
int linearinterpolation(int a, int b, int t)
Linearly interpolates between two values based on a specified index (t).
The FS package is for file system operations, such as reading and writing files.
Reference
str readfile(str path)
Reads the content of a file at the given path.
str* readfilelines(str path)
Reads all lines from a file at the given path.
void writefile(str path, str cont)
Writes the specified content to a file at the given path.
void writefilelines(str path, str* cont)
Writes the specified lines to a file at the given path.
bool exists(str path)
Checks if a file or directory exists at the given path.
void delete(str path)
Deletes a file at the given path.
void createdir(str path)
Creates a directory at the given path.
void deletedir(str path)
Deletes a directory at the given path.
str* listdir(str path)
Lists all files in a directory at the given path.
str* listdirs(str path)
Lists all directories in a directory at the given path.
Parse is a universal package for parsing JSON, XML, HTML and for Regex operations.
Reference
obj Json.loadjson(str json)
Loads a JSON string and converts it into a Carp object.
MatchResult Regex.match(str pattern, str text)
Converts a dynamic object into a Carp object.
MatchResult* Regex.matches(str pattern, str text)
Matches a regular expression pattern against a text and returns all matches.
str Regex.replace(str pattern, str text, str replacement)
Replaces all occurrences of a regular expression pattern in a text with a replacement string.
str* Regex.split(str pattern, str text)
Splits a text into an array of strings based on a regular expression pattern.
bool Regex.test(str pattern, str text)
Tests if a regular expression pattern matches a text.
Net is for network operations, such as HTTP requests.
Reference
str Http.get(str url)
Makes a GET request to the specified URL.
str Http.post(str url, str content)
Makes a POST request to the specified URL with the specified content.
Resource is for loading and managing external resources (the ones stored in /resources), such as text files.
Reference
obj load(str name)
Loads a resource with the specified name.
obj list()
Lists all the resources in the package.
System is for system operations, such as executing shell commands.
Reference
str runcommand(str command)
Executes a command in the command prompt and returns the output.
str username
Gets the username of the current user.
str machine_name
Gets the name of the machine.
str os_version
Gets the version of the operating system.
int processor_count
Gets the number of processors on the current machine.
Projects are designed for larger codebases, they may contain multiple files, and have resources stored within them. They can later be packaged into .caaarp
files for distribution and execution.
To create a new project, run the following command:
$ carp new -n my_project
Or you can convert an existing script into a project using:
$ carp new -s my_script.carp
Optionally the name of the project can be specified with the -n
flag.
To run a project, navigate to the project directory and run:
$ carp .
This will build and run the project. To just build it, use:
$ carp build .
These will generate a .caaarp
file in the /export directory, named the projects name suffixed with the current version in the .carpproj
file.
A project will have all its scripts in the initial directory, and resources in the /resources directory. The /export directory will contain the generated .caaarp
files.
The project configuration is stored in a .carpproj
file in the project directory. It contains the project name, version, and other metadata.
-i, --interactive Start the Carp REPL after executing the script.
-c, --line Execute the Carp code provided in the command line.
-v, --verbose (Default: true) Print the result of the script execution to the console.
-d, --debug Start the Carp debugger.
-f, --force-throw Force the internal errors to trigger the native stacktrace.
--strict-warnings Treat warnings as errors
--default-non-structs (Default: true) Allow non-struct objects to be auto-initialized
--implicit-casts (Default: true) Enable implicit casting/type coercion
--help Display this help screen.
--version Display version information.
Path (pos. 0) The path to the script, project or package to run.
The preprocessor is responsible for formatting the input code for the lexer and parser. It adds support for directives, strips comments, re-writes imports and can be expanded in the future to do even more.
The include directive, directly includes the specified file path in the current file. Note that this is not package-safe and will only use the current directory as a source, which makes it helpful for the REPL. It will also be preprocessed before being injected.
#include header.carp
Define allows you to create macros, which are similar to methods except they directly replace tokens and their arguments with the body. Brackets can be used to allow it to span multiple lines
#define add(a, b) = (
a + b
)
int sum = add(1, 2)
# This will be replaced with 1 + 2
It can also be used for things such as constants, as it does not need to take parameters
#define my_const = 25.5
my_const
# Replaced with 25.5
The shorthand block body should be used whenever possible (whenever the body is a single expression).
# Single-line block body
int add(int a, int b) -> a + b
# Multi-line block body
int slowmultiply(int a, int b) {
int result = 0
for b -> result = add(result, a)
return result
}
Variables and properties are named in snake_case. Classes are named in upper CamelCase. Methods and functions are lower mergecase.
For example:
int my_var = 10
void print(str message) -> IO.println(message)
class MyObj {
void dosomething() -> print('Hello, world!')
}