Skip to content
Wesley Yan Soares Brehmer edited this page Sep 15, 2024 · 25 revisions
 _       __           __  
| |     / /_  _______/ /_ 
| | /| / / / / / ___/ __ \
| |/ |/ / /_/ (__  ) /_/ /
|__/|__/\__, /____/_.___/ 
       /____/

Official documentation

Wysb Programming Language Documentation

Syntax

The syntax of Wysb is heavily influenced by other C-like languages, particularly Go, JavaScript, and PHP. It is crafted to feel intuitive for developers transitioning from these languages, prioritizing simplicity and predictability.

Scripts are written in plain text files with a .wys file extension. Wysb doesn't require pre-compilation: the code is executed directly from the source, running sequentially from top to bottom like typical scripting languages.


Comments

Single-line comments start with // and continue until the end of the line.

// This is a single-line comment

Multi-line or block comments are enclosed within /* ... */, allowing them to span multiple lines.

/*
This is a block comment.
*/

Comments, treated like whitespace, are ignored during execution. While single-line comments are standard, block comments are helpful when you need to disable sections of code or write comments within expressions.


Reserved Keywords

Wysb includes a concise set of reserved keywords used for predefined functions and control structures. These words must not be used as identifiers in your scripts.

function  true  false  if  else  return  while  and  or

Identifiers

The rules for naming identifiers in Wysb are similar to those of other programming languages. An identifier must begin with either a letter or an underscore (_), followed by any combination of letters, digits, and underscores. Identifiers are case-sensitive.

exampleName
camelCase
PascalCase
_underscore_variable
abc123
ALL_CAPS

Blocks

In Wysb, curly braces {} are used to define blocks of code. Blocks can be placed wherever a statement is expected, such as within control flow constructs.

weight = 5.4

if (weight > 4.4) {
  print("Warning: The weight has increased a lot.")
}

Here’s a reworked and reworded version of the text, with "Wysb" replacing "Ghost" and other significant changes:


Values

In Wysb, everything is built from basic object types. All values are treated as objects and may come with their own methods for manipulation. Let’s briefly introduce some key types.


Booleans

Booleans represent logical true or false conditions. Wysb has two boolean values: true and false.


Lists

Lists in Wysb are ordered collections of elements that can be of mixed types, each accessible by its index. Lists are created as comma-separated elements and enclosed in square brackets. They can store any kind of value:

[
  "Wysb",
  57.3,
  function (x) {
    return x * x
  },
]

Each element in a list is accessed by its numerical index, starting from zero.


Maps

Maps, sometimes known as associative arrays, dictionaries, or hashes in other languages, store key-value pairs. Each pair is separated by a colon and the entire structure is enclosed in curly braces. The keys can be strings or other primitive types, and the values can be of any type.

{
    "name": "Wysb",
    "value": 57.3,
    "handler": function(x) { return x * x }
}

Null

Like many programming languages, Wysb has a special value null, representing the absence of a value. When a function returns nothing, its return value is null.


Numbers

Wysb simplifies working with numbers by using a single numeric type: decimals. While other languages might differentiate between integers, floats, and doubles, Wysb opts for a single type to make handling numbers straightforward.

Though decimals may be slightly slower than pure integers, Wysb's arbitrary-precision, fixed-point system makes its numbers extremely accurate. Number representations are similar to what you’d expect in other languages:

0
1234 - 5678
3.14159
1.0 - 12.34

Strings

Strings in Wysb represent sequences of bytes, and their length is always non-negative. Strings can be enclosed in either single or double quotes and can handle Unicode characters.

string = "Hello, world!"

Variables

Variables in Wysb are named placeholders for storing data. You can create a new variable using the = operator. For example:

x = 3 * 4

This statement creates a new variable x in the current scope and assigns it the result of the expression after the =. Once defined, the variable can be accessed by its name, as shown below:

device = "QuantumProcessor"

print(device)  // >> QuantumProcessor

Scope

Wysb enforces block-level scope, meaning a variable is valid from the point it's defined until the end of the block where it was declared.

function demo() {
  print(value)  // Error: "value" is not yet defined.

  value = 42

  print(value)  // >> 42
}

print(value)  // Error: "value" is no longer in scope.

Variables declared at the global level (top-level of the script) are global, while those defined inside functions or blocks are local. It's possible to "shadow" a variable by declaring another one with the same name in a nested scope without causing an error:

name = "global"

function demo() {
  name = "local"

  print(name)  // >> local
}

print(name)  // >> global

Operators

An operator in Wysb is an element that takes one or more values (or expressions) and produces a new value, forming an expression in itself.

Operators are categorized by the number of values they accept. Unary operators require a single value, such as ! (logical not). Binary operators require two values, like the arithmetic operators + (addition) and - (subtraction). Most of Wysb's operators are binary.


Precedence and Associativity

Operator precedence defines how "strongly" an operator binds to its operands. For instance, in the expression 2 + 3 * 4, the result is 14 because multiplication (*) has higher precedence than addition (+). You can use parentheses to override precedence, like in (2 + 3) * 4, which evaluates to 20.

Associativity determines how operators of the same precedence are grouped. For example, subtraction (-) is left-associative, meaning 5 - 3 - 1 is grouped as (5 - 3) - 1, which results in 1. Assignment (=), on the other hand, is right-associative, so x = y = z is grouped as x = (y = z).

While operators follow a specific precedence and associativity, adding parentheses can make the code more readable and explicit about how operations are grouped.

The following table summarizes Wysb's operator precedence from highest to lowest. Operators in the same row have equal precedence:

Precedence Operator Description Associativity
1 () . Grouping, Method call Left
2 - Negation Right
3 * / % Multiply, Divide, Modulo Left
4 + - Add, Subtract Left
5 < <= > >= Comparisons Left
6 == != Equals, Not equal Left
7 and Logical and Left
8 or Logical or Left
9 = Assignment Right

Arithmetic Operators

Basic arithmetic in Wysb works just like the arithmetic you learned in school. Here are some examples:

Example Name Result
-a Negation Opposite of a
a + b Addition Sum of a and b
a - b Subtraction Difference between a and b
a * b Multiplication Product of a and b
a / b Division Quotient of a divided by b
a % b Modulo Remainder of a divided by b

The modulo operator (%) retains the sign of the dividend. For example:

print(7 % 4)  // >> 3
print(7 % -4) // >> 3
print(-7 % 4) // >> -3
print(-7 % -4) // >> -3

Assignment Operator

The assignment operator in Wysb is =. It assigns the value of the expression on the right to the variable on the left.

greeting = "Hello, Wysb!"

print(greeting)  // >> Hello, Wysb!

Comparison Operators

Comparison operators allow you to evaluate relationships between values. Here's a summary:

Example Name Result
a == b Equal true if a is equal to b
a != b Not equal true if a is not equal to b
a < b Less than true if a is less than b
a > b Greater than true if a is greater than b
a <= b Less than or equal true if a is less than or equal to b
a >= b Greater than or equal true if a is greater than or equal to b

Here’s the adapted version for Wysb with modified examples and syntax:


Control Flow

Control flow allows you to decide which parts of the code get executed and how often. Conditional statements decide whether a block of code should run, while loops repeat actions until certain conditions are met.


Truthiness

In Wysb, every value has an inherent truthiness. Values like 0, false, or null are considered falsy, while most others are truthy. These evaluations are useful for logical expressions and condition checks.


Logical Operators

Wysb features two special logical operators: and and or. These operators use short-circuit evaluation, meaning they evaluate the left operand first and, depending on its value, may skip evaluating the right operand.

  • The and operator evaluates the left-hand side. If it’s falsy, it returns that value immediately. If it's truthy, it evaluates and returns the right-hand value.
print(false and 10)  // >> false
print(true and 5)    // >> 5
  • The or operator works the opposite way. If the left-hand side is truthy, it’s returned; otherwise, the right-hand value is evaluated and returned.
print(false or 10)   // >> 10
print(true or 5)     // >> true

Conditional Statements

Conditional statements allow you to execute certain blocks of code based on whether a condition is true or false.


If Statements

The simplest form of conditional control in Wysb is the if statement. It checks if a condition is true and, if so, executes the corresponding block of code.

if (condition) {
  // Execute this block if condition is true
}

You can also add an else clause to handle cases where the condition is false.

if (condition) {
  // Do this if condition is true
} else {
  // Do this if condition is false
}

For multiple conditions, you can use else if to chain them:

if (firstCondition) {
  // Do this if firstCondition is true
} else if (secondCondition) {
  // Do this if secondCondition is true
} else {
  // Do this if none of the above are true
}

Switch Statements

The switch statement allows you to evaluate a single expression and match its result against several cases, making it more concise than multiple if-else statements.

switch (value) {
  case 1 {
    // Handle case where value is 1
  }
  case 2 {
    // Handle case where value is 2
  }
  default {
    // Handle all other cases
  }
}

In Wysb, cases do not fall through like in some other languages. Only the first matching case is executed. You can also group multiple cases together:

switch (value) {
  case 0, 1, 2 {
    // Handle cases where value is 0, 1, or 2
  }
  case 3, 4, 5 {
    // Handle cases where value is 3, 4, or 5
  }
}

Looping Statements

Loops are useful for executing code multiple times. Wysb offers several ways to create loops.


While Statement

The while loop repeatedly executes a block of code as long as a specified condition is true.

while (condition) {
  // Do something as long as condition is true
}

For example, the following loop increments x while n is less than 5:

n = 0
x = 0

while (n < 5) {
  n = n + 1
  x += n
}

print(x)  // >> 15

For Statement

The for loop in Wysb works similarly to C-style loops. It consists of an initializer, a condition, and an increment.

for (initializer; condition; increment) {
  // Do something
}

Here’s an example that counts from 1 to 5:

for (i = 1; i <= 5; i = i + 1) {
  print(i)
}

Break Statement

The break statement immediately exits the nearest enclosing loop, skipping any further iterations.

function testBreak(x) {
  i = 0

  while (i < 6) {
    if (i == 3) {
      break
    }
    i = i + 1
  }

  return i * x
}

print(testBreak(2))  // >> 6

Continue Statement

The continue statement skips the rest of the current iteration and moves on to the next one.

i = 0
n = 0

while (i < 5) {
  i = i + 1

  if (i == 3) {
    continue
  }

  n = n + 1
}

print(n)  // >> 4

In this example, when i equals 3, the loop skips the rest of the iteration and increments i, so n is not updated for that cycle. Here is the version in English with examples and syntax adapted for Wysb:


Functions

Like in many dynamic languages, functions are first-class values in Wysb. This means they can be stored in variables, passed as arguments to other functions, and returned as results. This gives great flexibility when using functions in the language.


Defining Functions

You can define functions in Wysb using the function keyword, followed by a list of parameters and a block of code:

function sum(a, b) {
  print(a + b)
}

The body of a function is always a block of code. To return a value, you use the return statement inside the function.

function sum(a, b) {
  return a + b
}

Calling Functions

Once you have a function, calling it is simple: just pass the required parameters along with the function name.

result = sum(2, 3)
print(result)  // >> 5

The value assigned to the variable result is the result of the function execution, either from an explicit return or from the last evaluated value in the function body.


Anonymous Functions

In Wysb, functions can also be anonymous, meaning they don’t have a name and can be directly assigned to variables. These functions can be passed as arguments to other functions, stored, or returned.

add = function(a, b) {
  return a + b
}

print(add(3, 4))  // >> 7

First-Class Functions

Functions in Wysb are first-class values, meaning they can be used like any other value. They can be assigned to variables, passed as arguments, or even returned by other functions.

function identity(x) {
  return x
}

function multiply(a, b) {
  return a * b
}

result = identity(multiply)(3, 5)
print(result)  // >> 15

In the example above, the identity function returns the multiply function, which is then called with the arguments (3, 5).


Local Functions

In Wysb, you can declare local functions within other functions. These functions are scoped locally and can only be accessed inside the function that defines them.

function outerFunction() {
  function localFunction() {
    print("I'm local!")
  }

  localFunction()  // >> I'm local!
}

outerFunction()

The localFunction exists only within the scope of outerFunction and cannot be accessed from outside.


Functions Returning Functions

Just like you can pass functions as arguments, in Wysb, you can return a function from another function.

function returnFunction() {
  word = "returned"

  function innerFunction() {
    print(word)
  }

  return innerFunction
}

newFunction = returnFunction()
newFunction()  // >> returned

In the example above, the returnFunction returns the innerFunction, which still has access to the variable word even after returnFunction has executed. Here is the version in English with examples and syntax adapted for Wysb:


Error Handling

Errors are an inevitable part of writing and running code, and Wysb provides mechanisms for detecting, reporting, and managing errors. In this section, we'll explore the different types of errors you might encounter and how Wysb handles them.


Syntax Errors

The first type of errors you are likely to encounter are syntax errors. These occur when the code you write doesn't follow the grammar rules of Wysb. For example:

1 + * 2

When Wysb encounters a syntax error, it will provide a helpful error message that points out where the problem is. For example:

[line 1] Error at '*': Expecting a valid expression.

Another common type of syntax error is using an undefined variable:

print(message)

Wysb will report this as:

1:7:repl.wysb: runtime error: unknown identifier: message

Unlike some other languages, Wysb catches these kinds of errors before it tries to run the code. This ensures that your code is free from syntax and scope-related issues before execution begins.


Runtime Errors

Even after your code passes the syntax check, there could still be errors that only occur while the code is running. These are called runtime errors. Runtime errors typically happen when your code tries to perform an action that Wysb cannot handle.

For instance, if you attempt to call a method that doesn’t exist on an object, Wysb will throw a runtime error. Consider the following example:

class Foo {
    function bar() {
        // some code
    }
}

foo = Foo.new()

foo.someUndefinedMethod()

Running this will generate the following error:

1:4:repl.wysb: runtime error: undefined method someUndefinedMethod for class Foo

Wysb halts execution immediately after encountering a runtime error. Unlike some languages that try to continue executing after an error occurs, Wysb stops to ensure you’re aware of the problem.


Creating Runtime Errors

In some cases, you may want to deliberately trigger a runtime error in your code. Wysb allows you to do this using the abort() function. This can be useful for ensuring that certain conditions are met or for debugging purposes.

abort("Something went wrong!")

This will immediately stop the program and print the following error:

runtime error: Something went wrong!

You must pass a string as the error message. If no message is provided, or if it's null, the abort() function will not raise an error.

Here's the section on Classes translated into Wysb with similar syntax and examples:


Classes

Classes in Wysb define the behavior and state of objects. The behavior is determined by methods within the class, and each object of the same class supports these methods. The state is maintained through properties, which are unique to each instance of the class.


Defining a Class

You create a class using the class keyword:

class CoffeeMaker {
  //
}

This creates a class named CoffeeMaker with no methods or properties.


Methods

To add functionality to your class, you define methods:

class CoffeeMaker {
    function brew() {
        print("Your coffee is now brewing.")
    }
}

Here, the brew method prints a message. You can also add parameters to methods:

class CoffeeMaker {
    function brew(dosage, temperature) {
        print("Your %s of coffee is now brewing at %s degrees.".format(dosage, temperature))
    }
}

Method Scope

In object-oriented languages like Wysb, there is another kind of scope: object scope. When you call a method on an object, you're accessing the method in the object's scope.

For example:

CoffeeMaker.brew()

This looks up the brew method in the scope of the CoffeeMaker class.


The this Keyword

Within a method, you often need to refer to the current instance of the object. You can do this using this:

class CoffeeMaker {
    function setGrind(grind) {
        this.grind = grind
    }

    function printGrind() {
        this.setGrind("course")
        print(this.grind)
    }
}

this refers to the instance of the class whose method is currently being executed. It allows you to access and modify properties and call other methods on the same instance.

You cannot use this outside of a method, but inside a method, this always refers to the current instance:

class CoffeeMaker {
    function setGrind(grind) {
        this.grind = grind
    }

    function printGrindThrice() {
        this.setGrind("course")
        for (i in 1 .. 3) {
            print(this.grind)
        }
    }
}

In Wysb, this retains its reference to the original object even within callbacks.


Constructors

To create instances of a class, you define a constructor. The constructor is a special method that initializes new objects:

class CoffeeMaker {
    function constructor(grind, temperature) {
        print("Grind set to: %s".format(grind))
        print("Temperature set to: %s".format(temperature))
    }
}

When you create a new instance, Wysb automatically calls the constructor:

drip = CoffeeMaker.new("flat", "200")
chemex = CoffeeMaker.new("coarse", "202")

The constructor initializes the instance with specified values and sets properties accordingly.


Properties

Properties store the state of an instance and are similar to variables but are bound to the instance:

class CoffeeMaker {
    function constructor(grind, temperature) {
        this.grind = grind
        this.temperature = temperature
        this.printSettings()
    }

    function printSettings() {
        print("Grind set to: %s".format(this.grind))
        print("Temperature set to: %s".format(this.temperature))
    }
}

Properties like this.grind and this.temperature are unique to each instance.


Inheritance

A class can inherit from another class using the extends keyword. This allows the new class to use methods and properties from the parent class:

class Bar extends Foo {
  //
}

The Bar class inherits from Foo and can use its methods and properties.


Traits

Wysb also supports traits, which are like classes but cannot be instantiated. Traits are used to share methods and properties between classes:

trait Brewable {
  function brew() {
    print("Your coffee is now brewing.")
  }
}

To use a trait in a class, use the use keyword:

class CoffeeMaker {
  use B
}

You can include multiple traits by separating them with commas:

class CoffeeMaker {
  use C, B
}

This way, CoffeeMaker gets methods from both C and B.

Here's the section on Method Calls adapted to Wysb with new examples but respecting the syntax, semantics, and rules of the language:


Method Calls

Wysb is highly object-oriented, meaning that most of the code you write will involve calling methods on objects. A typical method call looks like this:

cat.purr("Happy purring!")

In this example, you have a receiver expression (cat), followed by a . (dot), then a method name (purr), and an argument list in parentheses (("Happy purring!")). If there are multiple arguments, they are separated by commas:

cat.perform("jump", "high")

The argument list can also be empty:

cat.sleep()

When Wysb executes a method call, it follows these steps:

  1. Evaluate the Receiver and Arguments: It evaluates the receiver and arguments from left to right.
  2. Look Up the Method: It searches for the method on the object that the receiver refers to.
  3. Invoke the Method: It calls the method with the provided argument values.

Here's how to structure the documentation for modules in Wysb:


Modules

Wysb uses a straightforward module system to organize and manage code in separate files.

Each Wysb file operates as its own module with its own scope. Importing a file into another does not explicitly merge their scopes. This means two modules can define top-level variables with the same name without causing conflicts.

Shared Imports

Wysb manages imported files efficiently. Importing a module in different places does not reload or re-evaluate that module each time. The first import of a module is the only instance where it is loaded and evaluated.

Binding Variables

All top-level variables within a module can be exported. To import specific values, you list the identifiers in your import statement:

import Request, Response from "network"

This imports and binds the Request and Response variables from the network module, making them available in your file.

Aliases

You can import a variable under a different name using the as keyword:

import str as isString from "utils"

Importing All Values

To import every value from a module, use the * wildcard:

import * from "utils"

Cyclic Imports

Wysb supports cyclic imports to some extent, though they should be avoided if possible. Wysb tracks imported modules and can handle cyclic imports without getting stuck in an infinite loop:

// main.wys
import "a"

// a.wys
print("start a")
import "b"
print("end a")

// b.wys
print("start b")
import "c"
print("end b")

// c.wys
print("start c")
import "a"
print("end c")

Running the above code will produce:

start a
start b
start c
end c
end b
end a

The program prints the output correctly without falling into an infinite loop.

Importing for Side Effects

You can import a module solely for its side effects, without importing any values:

import "config"

This will execute the global code of the module but won’t bind any new variables.

Example Usage

Here's a basic example to illustrate how modules work in Wysb:

File Structure:

main.wys
modules/
    functions.wys
    variables.wys

main.wys

import * from "modules/functions"
import * from "modules/variables"

print("Program loaded.")

add(one, two)
greet(hello)

functions.wys

function add(a, b) {
  print(a + b)
}

function greet(message) {
  print(message)
}

variables.wys

one = 1
two = 2
hello = "Hello, world!"

This example demonstrates how to load modules easily. Modules in Wysb are just files with the .wys extension and can be organized in any directory within your project. In this example, we used a modules directory for clarity.

Standard Objects

Lists

Lists in Wysb are ordered collections of elements that can be of various types, identified by a numerical index. You can create a list using square brackets and separate elements with commas. For example:

[
  "Wysb",
  42,
  function (x) {
    return x + 1
  },
]

Accessing Elements

To access elements in a list, use the subscript operator with the index of the element. Note that indices start at zero:

words = ["begin", "continue", "end"]

print(words[0]) // >> begin
print(words[1]) // >> continue
print(words[2]) // >> end

Methods

Here are the methods available for lists in Wysb:

  • first()
    Returns the first element in the list. If the list is empty, it returns null:

    [5, 10, 15].first()
    // >> 5
    
  • join(separator)
    Joins the items in a list into a string, using the specified separator:

    [5, 10, 15].join('-')
    // >> 5-10-15
    
  • last()
    Returns the last element in the list. If the list is empty, it returns null:

    [5, 10, 15].last()
    // >> 15
    
  • length()
    Returns the number of elements in the list:

    [5, 10, 15].length()
    // >> 3
    
  • pop()
    Removes the last element from the list and returns it:

    list = [5, 10, 15]
    
    list.pop()
    // >> 15
    
    print(list)
    // >> [5, 10]
    
  • push(element)
    Adds an element to the end of the list:

    list = [5, 10]
    
    list.push(15)
    
    print(list)
    // >> [5, 10, 15]
    
  • tail()
    Returns a new list containing all elements except the first:

    [5, 10, 15].tail()
    // >> [10, 15]
    
  • toString()
    Returns a string representation of the list:

    [5, 10, 15].toString()
    // >> [5, 10, 15]
    

Maps

Maps in Wysb, also known as associative arrays, hashes, or dictionaries in other languages, store collections of key-value pairs. Maps are constructed using curly braces with comma-separated key-value pairs. Each pair is separated by a colon.

{
    "title": "Wysb",
    "version": 1.0,
    "function": function(x) { return x + 10 }
}

Accessing Elements

To access an element in a map, use the subscript operator with the key:

ages = { Alice: 30, Bob: 25, Charlie: 35 }

print(ages["Alice"]) // >> 30
print(ages["Bob"])   // >> 25
print(ages["Charlie"]) // >> 35

If you attempt to access a key that does not exist in the map, it will return null:

print(ages["David"]) // >> null

Strings

Strings are used for representing text data in Wysb.

Creating Strings

Strings can be created using either single or double quotes:

text1 = "A sample string"
text2 = 'Another example string'

Comparing Strings

Strings can be compared using the == operator. This comparison is case-sensitive:

text1 = "hello"
text2 = "world"

// false
result = text1 == text2

Long Strings

To handle long strings that span multiple lines, you can concatenate strings using the + operator:

longText = "This is a long string that needs " +
            "to be split across multiple lines " +
            "to keep the code readable."

Methods

Here are some useful string methods in Wysb:

  • find()

    Finds the first occurrence of a pattern in the string using a regular expression:

    pattern = "I need (.*)"
    input = "I need tea"
    
    result = pattern.find(input)
    
    // expected output: ["tea"]
    
  • format()

    Formats a string according to specified placeholders:

    name = "Alex"
    age = 25
    
    message = "%s is %s years old.".format(name, age)
    
    // expected output: "Alex is 25 years old."
    
  • endsWith()

    Checks if the string ends with a specified value:

    result = "This is my hobby".endsWith("hobby")
    
    // expected value: true
    
  • length()

    Returns the length of the string:

    length = "Programming".length()
    
    // expected value: 11
    
  • replace()

    Replaces occurrences of a substring with a new substring:

    replaced = "Code 1.0".replace("1.0", "2.0")
    
    // expected value: "Code 2.0"
    
  • split()

    Splits the string into a list based on a delimiter:

    parts = "apple, banana, cherry".split(", ")
    
    // expected value: ["apple", "banana", "cherry"]
    
  • startsWith()

    Checks if the string starts with a specified value:

    result = "This is my name".startsWith("This")
    
    // expected value: true
    
  • toLowerCase()

    Converts the string to lowercase:

    lower = "HELLO".toLowerCase()
    
    // expected value: "hello"
    
  • toUpperCase()

    Converts the string to uppercase:

    upper = "hello".toUpperCase()
    
    // expected value: "HELLO"
    
  • toString()

    Converts the value to a string (this is useful for ensuring the value is a string):

    str = 123.toString()
    
    // expected value: "123"
    
  • toNumber()

    Converts the string to a number if the string represents a valid number:

    number = "42.5".toNumber()
    
    // expected value: 42.5
    
  • trim()

    Removes whitespace from both ends of the string:

    trimmed = "  Wysb  ".trim()
    
    // expected value: "Wysb"
    
  • trimEnd()

    Removes whitespace from the end of the string:

    trimmedEnd = "  Wysb  ".trimEnd()
    
    // expected value: "  Wysb"
    
  • trimStart()

    Removes whitespace from the start of the string:

    trimmedStart = "  Wysb  ".trimStart()
    
    // expected value: "Wysb  "
    

Numbers

Wysb uses a single numeric type: arbitrary-precision fixed-point decimals. This approach simplifies usage compared to having multiple numeric types such as integers and floats, though it might have a minor impact on performance.

Number Values

Numbers in Wysb are represented as fixed-point decimals, which ensures high precision. Examples of numeric values are:

0
5678
-1234
0.01
2.71828
45.67
-0.001

Precision

Unlike binary floating-point types, which can introduce rounding errors in fractional decimal representations, Wysb maintains accuracy in its arithmetic operations. For example:

value = 0

for (i = 0; i < 1000; i = i + 1) {
    value = value + 0.01
}

print(value)

// expected value: 10

This code snippet will accurately print 10, avoiding the rounding issues seen in languages with binary floating-point types.

Wysb numbers can represent values with up to 2^31 digits after the decimal point.

Scientific Notation

You can express numeric values in scientific notation using e, which denotes a power of 10:

1.2       // expected value: 1.2
1.2e0     // expected value: 1.2
1.2e1     // expected value: 12.0
1.2e2     // expected value: 120.0
1.2e3     // expected value: 1200.0
5e-2      // expected value: 0.05

Methods

Here are some methods available for numeric values in Wysb:

  • round()

    Rounds the number to the nearest integer or specified precision:

    rounded = 456.78.round()
    
    // expected value: 457
    
    rounded = 456.789.round(2)
    
    // expected value: 456.79
    
  • toString()

    Converts the number to its string representation:

    stringValue = 3.14159.toString()
    
    // expected value: "3.14159"
    

Standard Library

Console

The console object provides methods for outputting different types of messages to the console. These methods help in debugging, logging, and reporting information and errors.

Methods

  • console.debug()

    Outputs a debug message to the console, useful for detailed troubleshooting information.

    console.debug("This is a debug message")
    
  • console.error()

    Outputs an error message to the console, typically used for reporting errors or problems that need attention.

    console.error("This is an error message")
    
  • console.info()

    Outputs an informative message to the console, used for general information and status updates.

    console.info("This is an info message")
    
  • console.log()

    Outputs a general logging message to the console. This is the most commonly used method for general-purpose logging.

    console.log("This is a log message")
    
  • console.warn()

    Outputs a warning message to the console, indicating potential issues or warnings that are not necessarily errors but should be noted.

    console.warn("This is a warning message")
    

HTTP

The http object provides methods for handling HTTP requests and starting an HTTP server. These methods facilitate building web servers and handling incoming requests.

Methods

  • http.handle()

    Registers a new handler for a specified URL pattern. This method maps incoming requests to a handler function that processes the request and generates a response.

    http.handle("/", function(request) {
        print("hello world")
    })
    
  • http.listen()

    Starts an HTTP server at the given port and address. Executes the provided callback function when the server successfully starts.

    http.listen(3000, function() {
        print("Server started at http://localhost:3000 🌱")
    })
    

I/O

The io object provides methods for handling file operations, including appending, reading, and writing content to files. These methods facilitate file manipulation and data management within your Wysb programs.

Methods

  • io.append()

    Appends content to the end of the specified file. This method adds new data to the existing file without altering its current contents.

    io.append('./log.txt', 'message from wysb')
    
  • io.read()

    Reads and returns the entire contents of the specified file as a string. This method allows you to access the data stored in a file.

    content = io.read('./log.txt')
    
  • io.write()

    Writes content to the specified file, completely overriding any existing data. This method replaces the file's contents with the new data provided.

    io.write('New content for the file', './log.txt')
    

JSON

The json module provides methods for converting values to and from JavaScript Object Notation (JSON), a widely used format for data interchange. JSON supports serialization of maps, lists, numbers, strings, booleans, and null values. It is both language-agnostic and commonly utilized in various programming environments.

Methods

  • json.encode()

    Encodes a value into a JSON string. The value provided must be a map or a list.

    json.encode({ name: 'Alice', age: 30 })
    // '{"name":"Alice","age":30}'
    
    json.encode([10, 20, 30])
    // '[10,20,30]'
    
  • json.decode()

    Decodes a JSON string into its corresponding value. The input string must be valid JSON.

    json.decode('{"name":"Alice","age":30}')
    // { name: 'Alice', age: 30 }
    
    json.decode('[10,20,30]')
    // [10, 20, 30]
    

Math

The math module provides a set of functions and constants for performing mathematical operations and calculations.

Methods

  • math.abs()

    Returns the absolute (non-negative) value of a given number.

    math.abs(-100)
    // expected value: 100
    
  • math.cos()

    Returns the cosine of the specified radian value.

    math.cos(math.pi / 2).round()
    // expected value: 0
    
  • math.isNegative()

    Determines if the specified value is negative.

    math.isNegative(-100)
    // expected value: true
    
  • math.isPositive()

    Determines if the specified value is positive.

    math.isPositive(100)
    // expected value: true
    
  • math.isZero()

    Determines if the specified value is zero.

    math.isZero(5)
    // expected value: false
    
  • math.max()

    Returns the largest of the two given numbers.

    math.max(100, 200)
    // expected value: 200
    
  • math.min()

    Returns the smallest of the two given numbers.

    math.min(100, 200)
    // expected value: 100
    
  • math.sin()

    Returns the sine of the specified radian value.

    math.sin(math.pi).round()
    // expected value: 0
    
  • math.tan()

    Returns the tangent of the specified radian value.

    math.tan(0).round()
    // expected value: 0
    

Properties

  • math.e

    The mathematical constant e = 2.718281…, which can be rounded to a desired precision.

    math.e.round(2)
    // expected value: 2.72
    
  • math.epsilon

    The mathematical constant 𝜀 = 2.220446049250e-16, representing the smallest difference from zero in floating-point numbers.

    math.epsilon
    // expected value: 0.00000000000000022204460492503130808472633361816
    
  • math.pi

    The mathematical constant π = 3.141592…, which can be rounded to a desired precision.

    math.pi.round(2)
    // expected value: 3.14
    
  • math.tau

    The mathematical constant τ = 6.283185…, which can be rounded to a desired precision.

    math.tau.round(2)
    // expected value: 6.28
    

OS

The os module provides methods and properties related to the operating system environment, including command-line arguments, time measurements, and system information.

Methods

  • os.args()

    Returns a list of command-line arguments passed to a Wysb script. The first element [0] is the script name.

    os.args()
    
  • os.clock()

    Returns the number of nanoseconds elapsed since January 1, 1970 UTC (Unix epoch).

    os.clock()
    // expected value: 1643344902998773000
    
  • os.exit()

    Causes the current program to exit with the given status code and optional message. Code zero typically indicates success, while non-zero codes indicate errors. The program terminates immediately.

    os.exit(0, "Process completed successfully")
    // expected output: Process completed successfully
    

Properties

  • os.name

    Returns the name of the current operating system. Possible values include:

    • darwin (macOS)
    • linux
    • windows
    os.name
    // expected value: darwin
    

Random

The random module provides methods for generating random numbers and controlling the randomness.

Methods

  • random.seed()

    Initializes the random number generator. This method accepts an optional seed value. If no seed is provided, the current time is used. To generate the same sequence of random numbers across multiple runs of your program, you should provide a specific seed value.

    random.seed()
    
  • random.random()

    Returns a random floating-point number between 0 and 1. You can optionally specify a minimum and maximum value. The returned value will be within the specified range, inclusive.

    random.random()
    // Returns a number between 0 and 1
    
    random.random(0, 10)
    // Returns a number between 0 and 10
    
    random.random(10)
    // Returns a number between 0 and 10 (default minimum is 0)
    

Properties

  • random.seed

    The current seed value of the random number generator.

    random.seed
    

Time

The time module provides methods and properties related to time, allowing you to work with time intervals and obtain the current time.

Methods

  • time.now()

    Returns the current time in seconds elapsed since January 1, 1970 UTC, known as the Unix epoch.

    now = time.now()
    
    // Example output: 1519316240
    
  • time.sleep()

    Pauses the execution of the program for the specified number of milliseconds.

    // Sleep for 1 second
    time.sleep(1000)
    

Properties

  • time.nanosecond

    The numerical value of a nanosecond in seconds.

    nanosecond = time.nanosecond
    
    // Expected value: 0.000000001
    
  • time.microsecond

    The numerical value of a microsecond in seconds.

    microsecond = time.microsecond
    
    // Expected value: 0.000001
    
  • time.millisecond

    The numerical value of a millisecond in seconds.

    millisecond = time.millisecond
    
    // Expected value: 0.001
    
  • time.second

    The numerical value of a second.

    second = time.second
    
    // Expected value: 1
    
  • time.minute

    The numerical value of a minute in seconds.

    minute = time.minute
    
    // Expected value: 60
    
  • time.hour

    The numerical value of an hour in seconds.

    hour = time.hour
    
    // Expected value: 3600
    
  • time.day

    The numerical value of a day in seconds.

    day = time.day
    
    // Expected value: 86400
    
  • time.week

    The numerical value of a week in seconds.

    week = time.week
    
    // Expected value: 604800
    
  • time.month

    The numerical value of a month in seconds.

    month = time.month
    
    // Expected value: 2592000 (approximate, assuming 30 days per month)
    
  • time.year

    The numerical value of a year in seconds.

    year = time.year
    
    // Expected value: 31536000 (approximate, assuming 365 days per year)