Esta página aún no está traducida al español. Leer la versión en inglés

Operators

Declaring operators in Antril - the built-in symbols, new symbols with their precedence and associativity, prefix and postfix operators, increment, conversions, generic operators and operator constraints.

Última actualización: 7 min de lectura Editar en GitHub

En esta página

A type declares operators with op. An operator is a static, public method of its type that the compiler finds by the types of the operands: no import is needed to use it.

op SYMBOL(T a, T b) : R [precedence(level)] [associativity(kind)] body     // binary
op SYMBOL(T a) : R body                                                     // one operand, either side
left op SYMBOL(T a) : R body                                                // before the operand: SYMBOL a
right op SYMBOL(T a) : R body                                               // after the operand: a SYMBOL
op (Target)(Source s) body                                                  // a conversion

The body is a block or => expression;. Every operator has a result type (AN0023).

AntrilRun
import System;

public value Money
{
	public:
		i64 Cents;
		new(i64 Cents);

		op +(Money a, Money b) : Money => new Money(a.Cents + b.Cents);
		op -(Money a) : Money => new Money(-a.Cents);
		op *(Money a, i64 k) : Money => new Money(a.Cents * k);
		op ==(Money a, Money b) : bool => a.Cents == b.Cents;
		op <(Money a, Money b) : bool => a.Cents < b.Cents;
		op ++(Money a) : Money => new Money(a.Cents + 1);
		op (i64)(Money m) => m.Cents;
		op (Money)(i64 cents) => new Money(cents);
}

Main()
{
	Money a = new Money(150);
	Money b = new Money(250);
	Console.WriteLine((a + b).Cents + " " + (-a).Cents + " " + (a * 3).Cents);
	Console.WriteLine((a == b) + " " + (a != b) + " " + (a < b));
	a++;
	++a;
	Console.WriteLine((i64)a);
	Money c = (Money)99i64;
	Console.WriteLine(c.Cents);
}
Terminal
larva run
400 -150 450
false true true
152
99

Rules

  • An operator is declared inside the type of one of its operands (AN0022 at namespace level).
  • It is always public and static, whatever its section says.
  • A symbol can be declared for several operand types; the call is resolved like an overload.
  • op == gives != too. On a class, a declared op == replaces comparison by identity.
  • One op ++ (or op --) serves both ++x and x++: the variable is replaced by the result; ++x has the new value and x++ the old one.
  • Operators of the primitive types are built in.
  • Compound assignment uses the operator: a += b is a = a + b, and needs op + to give the type of a (AN2099).

Built-in symbols

These symbols can be declared, with the number of operands shown; they keep the language's precedence:

Symbol Operands
+ - one or two
* / % & | ^ == != < <= > >= two
! ~ one
++ -- one

=, ., =>, ?, .., ..=, -> and ... cannot be operators (AN0021). &&, ||, ??, the shifts and the ranges are not declarable as built-in operators.

New symbols

A program can declare its own symbols: a run of the characters + - * / % & | ^ ~ ! < > = ? @ # $, written without spaces, such as ^^, <=>, <+> or #. A new binary symbol must say how strongly it binds, after its result type (AN1067):

Clause Takes
precedence(…) A member of PrecedenceLevel, a number, or a constant member of any integer enum (AN1069).
associativity(…) Associativity.Left (the default), Associativity.Right, or Associativity.None.

A higher level binds more strongly. The levels of PrecedenceLevel are:

Level Value Built-in operators at that level
Assignment 10 = += …
Conditional 20 ?:
Coalesce 30 ??
LogicalOr 40 ||
LogicalAnd 50 &&
BitwiseOr 60 |
BitwiseXor 70 ^
BitwiseAnd 80 &
Equality 90 == !=
Relational 100 < <= > >= is as
Range 110 .. ..=
Shift 120 << >>
Additive 130 + -
Multiplicative 140 * / %
Power 141

A number between two levels places a symbol between them: precedence(135) binds more strongly than + and less than *.

AntrilRun
import System;

public value N
{
	public:
		i64 X;
		new(i64 X);

		op +(N a, N b) : N => new N(a.X + b.X);
		op *(N a, N b) : N => new N(a.X * b.X);

		op ^^(N a, N b) : N precedence(PrecedenceLevel.Power) associativity(Associativity.Right)
		{
			i64 r = 1;
			loop (i64 i = 0..b.X)
				r = r * a.X;
			return new N(r);
		}

		op <=>(N a, N b) : i32 precedence(PrecedenceLevel.Relational) associativity(Associativity.None)
			=> a.X < b.X ? -1 : a.X > b.X ? 1 : 0;

		op <*>(N a, N b) : N precedence(135) => new N(a.X * 100 + b.X);
}

Main()
{
	N two = new N(2);
	N three = new N(3);
	Console.WriteLine((two ^^ three ^^ two).X);
	Console.WriteLine(two <=> three);
	Console.WriteLine((two + two <*> two * three).X);
}
Terminal
larva run
512
-1
208
  • ^^ is right-associative: 2 ^^ 3 ^^ 2 is 2 ^^ (3 ^^ 2).
  • With Associativity.None, a <=> b <=> c is error AN0027: write parentheses.
  • A symbol has one precedence: every declaration of it, in any type or library, agrees (AN1065). The built-in symbols keep theirs (AN1066).
  • When adjacent operator characters can be read several ways, the longest declared symbol wins: a*-b is a * (-b), and a<-b is a <- b only if <- is declared.
  • A program can use a symbol before the place where it is declared, and the symbols a library declares are known to every program that references it.

One-operand operators

An operator with one operand binds more strongly than any binary one and declares no precedence (AN1068).

Declaration Used as
left op #(T a) #a
right op !(T a) a!
op ~~(T a) ~~a or a~~
op -(T a), op !(T a), op ~(T a) the built-in prefix forms

left and right apply only to operators with one operand (AN0025).

Antril
public value N
{
	public:
		i64 X;
		new(i64 X);
		right op !(N a) : N
		{
			i64 r = 1;
			loop (i64 i = 1..=a.X)
				r = r * i;
			return new N(r);
		}
		left op #(N a) : i64 => a.X * 10;
}

N three = new N(3);
(three!).X;   // 6
#three;       // 30

Conversions

op (Target)(Source s) declares a conversion from Source to Target. It takes one operand (AN0026) and is used with a cast:

Antril
op (i64)(Money m) => m.Cents;
op (Money)(i64 cents) => new Money(cents);

i64 cents = (i64)price;
Money back = (Money)99i64;

A declared conversion is always explicit: Money m = 99i64; without a cast is an error.

Generic operators

An operator can have type parameters, written after the symbol. Each one must appear in an operand, so the call can infer it (AN1072):

Antril
public value Meters
{
	public:
		f64 V;
		new(f64 V);
		op *<T : number>(Meters m, T k) : Meters => new Meters(m.V * (f64)k);
}

(new Meters(2) * 3).V;   // 6

Operators as constraints

A type parameter can require an operator: T : op +(T, T) : T, T : left op ++(T) : T, or a conversion T : (i64)T. Inside the generic code the operator can then be used on T. See Generics.

¿Te resultó útil esta página?

No se guarda ninguna dirección; tu nombre de usuario sí, si iniciaste sesión.

Esc