Skip to content

Papyrus

Scrivener07 edited this page Jun 27, 2019 · 29 revisions
Table of Contents

Reference Notation

For simplicity's sake, these pages use a very simple set of notations to help denote what is a valid Papyrus script. These notations are listed here:

Notation Meaning
'text' Denotes text that should exist exactly in the definition, like a keyword or operator.
<text> Denotes some element for which there is another reference page. For example, "" would indicate any valid Papyrus identifier could go there.
[ ] Surrounds an optional element. It can exist, or not exist, but not more then one of them.
( ) Groups items together, just like in mathematics.
+ Means that there needs to be one or more of the preceding item
* Denotes that there should be zero or more of the preceding item
::= Separates the name of the rule on the left with the rule's definition on the right

Naming Conventions

The flexibility of Papyrus does not require a strict conformity to any particular Coding Conventions in regards to naming scripts and their members. The base scripts tend to use hungarian notation for Function parameters. The most important point is to remain consistent with naming and conventions. Read more about Coding Conventions.

Hungarian Notation

The hungarian notation coding convention is commonly encountered when working with base scripts. When coding with an IDE, the benefits of using hungarian notation become moot.

  • a --> function argument
  • k --> object
  • b --> boolean
  • i --> integer
  • f --> float
  • s --> string
  • p --> pointer (carryover from internal code, inconsistency, should not be used)
  • r --> reference (carryover from internal code, inconsistency, should not be used)
Example

In this case the notation ak from akActionRef indicates the variable is a "Function Argument Object".

 Function MyFunction(int myNum, Form akActionRef)

Casing

  • Properties tend to be upper CamelCase.
  • Script variables tend to be upper CamelCase.
  • Local variables tend to be lower camelCase.
  • ReferenceAlias properties always have the "Alias_" prefix.
  • Read-only properties are usually ALL_CAPS_WITH_UNDERSCORE.

Language Keywords

The following words are Papyrus keywords. Keywords are case-insensitive, and you may not have an Identifier that matches them.

Keywords
as Auto AutoReadOnly BetaOnly bool
Const CustomEvent CustomEventName DebugOnly Else
ElseIf EndEvent EndFunction EndGroup EndIf
EndProperty EndState EndStruct EndWhile Event
Extends False float Function Global
Group If Import is int
Length Native new none Property
return ScriptName ScriptEventName State string
Struct StructVarName true var While

Identifiers

<identifier> ::= (<letter> | '_') (<letter> | <digit> | '_')*

An identifier is simply a name in Papyrus that refers to a Variable, Function, Script, or Property. A valid identifier consists of a letter or underscore, optionally followed by several other letters, digits, or underscores.

Examples
; Valid identifiers
HelloWorld
_IAmCool
_4You
i
; Invalid identifiers
4you
*Star
BFargle%

Namespace Identifier

If the identifier refers to a script, it may have namespaces, separated by colons as follows:

<full script name> ::= <identifier> (':' <identifier>)*

The last identifier specifies the name of the script, and the identifiers in front of it are the namespaces. The namespaces are the folders that the script will be found in. For example, if you have a script in MyCoolStuff\Quests\MyQuest.psc the name of the script will be MyCoolStuff:Quests:MyQuest.

Examples
MyQuestScript
CivilWar:MainQuestScript
Traps:Triggers:TripWire

Operators

The following are valid operators in the Papyrus language:

 () [] ,  =  +  -
 *  /  %  .  "" !
 == != >  <  >= <=
 || && += -= *= /=
 %= as is
Operator Precedence

Operator precedence is listed in the following table, from highest to lowest.

Operator
() Mathematical parenthesis
. Dot operator (for accessing functions and properties)
as is Cast operators
- ! Unary minus and logical NOT
* / % Multiplication, division, and modulus
+ - Addition and subtraction
== != > < >= <= Comparison operators
&& Logical AND
|| Logical OR
= += -= *= /= %= Assignment

Parenthesis

()

Parenthesis, when surrounding an Expression, will cause that expression to be evaluated before any operators outside of the parenthesis. When following a Function name, they denote the parameter list for the function.

Examples:
; i will get the value of 9
i = (1 + 2) * 3
; call the function MyFunction with a parameter of 10
MyFunction(10)

Square Brackets

[]

Square brackets are used for Array. Either for accessing a specific array element, denoting the size of an array for a new one, or for denoting that a type is an array type.

Examples:
; i whatever is in element 0 of the array
i = myArray[0]
; define an array of size 10
int[] myArray = new int[10]

Comma

,

The comma operator separates parameters in a Function definition, or a function call.

Examples:
; call the function MyFunction with a parameter of 10 and 20
MyFunction(10, 20)

Math Operators

+ - * / %

Math operators perform some sort of operation on the two Expressions it sits between. Papyrus supports assignment, addition, subtraction, multiplication, division, and integer modulus. The minus sign is also used for negation. Also known as unary minus. The addition operator can also be used to concatenate ('paste together') strings.

Examples:
; i will get the value of 7
i = 1 + 2 * 3
; i will get the value of 1
i = 9 % 4
; i will get the string "Hello World"
i = "Hello " + "World"
Note

In the past, there was concern about the correctness of the modulo (%) operator when applied to negative integers such as -2,147,483,647 (0x80000001). Those concerns were unfounded.

The modulo operator produces correct results in all cases, but it must be understood that the result will have the sign of the dividend in Papyrus, therefore 3 % 2 == 1 and -3 % 2 == -1. This is similar to results in Java and many other languages.

A specific case of concern was the calculation necessary to isolate the low-order 3 bytes (6 nibbles, or 24 bits) of an integer, which requires a modulo operation with a divisor of 2^24. Also known as, 0x01000000 or 16,777,216.

When that modulo operation is applied to dividends in the range -2,147,483,647 (0x80000001) to -1 (0xFFFFFFFF), inclusive, the results correctly range from -16,777,215 (0xFF000001) to -1 (0xFFFFFFFF).

However, the goal of isolating the low-order 3 bytes of the integer, which is equivalent to performing a bit-wise AND with 0x00FFFFFF, is not met in those cases.

In order to meet that goal, negative values must be handled specially.

Example
;/
  Set the high 8 bits of `integer` to zero, and return the low 24 bits intact.
  Equivalent to "integer & 0x00FFFFFF" in C, Java, etc.
/;


int Function GetLow3Bytes(int integer)
{Set the high 8 bits of `integer` to zero, and return the low 24 bits intact.}
    If (integer < 0)
        ; Force `integer` to be a positive number, by changing its high bit (bit 32) from 1 to 0.
        integer += 0x80000000
    EndIf
    return integer % 0x01000000
EndFunction

Such emulation of bit-wise operations using mathematical operations is a little more complicated when the high bit must be preserved:

;/
  Return the high byte of `integer` as a value between 0 and 255 (0x00-0xFF), inclusive.
  Equivalent to `integer >> 24 & 0xFF` in C, Java, etc.
/;

int Function GetHighByteAsLowByte(int integer)
{Return the high byte of `integer`.}
  If (integer < 0)
    ; Force `integer` to be a positive number, by changing its high bit (bit 32) from 1 to 0.
    ; Then divide by 0x01000000 (16,777,216), which is now equivalent to a right shift of 24 bits.
    ; Now that it is bit 8 and cannot affect the sign, restore the value of the high bit we cleared.
    return (integer + 0x80000000) / 0x01000000 + 0x80
  EndIf
  return integer / 0x01000000
EndFunction

Dot

.

The dot operator goes after a variable name to let you call Functions on it, or to access Properties or Struct Members.

Examples:
; Call the function on the variable
myVariable.MyFunction()
; Set the property to 10
myVariable.MyProperty = 10

Double Quotes

""

Double quotes surround a string literal.

Examples:
; A basic hello world string
hello = "Hello World!"

Logical Operators

! || &&

Logical operators evaluate to true or false values, based on their Expressions.

  • The NOT operator (!) will be true if its single expression (to the left of the operator) is false, and false if it is true.
  • The OR operator (||) will be true if one of the expressions to its left and right are true, and will short-circuit if the left expression is true (it will not evaluate the right expression at all).
  • The AND operator (&&) will be true if both of the expressions to its left and right are true, and will short-circuit if the left expression is false (it will not evaluate the right expression at all).
Examples:
; i gets true if both a and b are true
i = a && b
; i gets true if a is false
i = !a

Comparison Operators

== != < > <= >=

The comparison operators compare both Expressions to their left and right, and return a boolean value based on the operator used. If floating-point values are being compared, a tiny epsilon value is accounted for to counteract floating-point error. If string variables are being compared, the comparison is case-insensitive.

  • Equality (==) returns true if both expressions have equal values.
  • Inequality (!=) returns true if both expressions have unequal values.
  • Less-than (<) returns true if the right expression's value is smaller then the left expression's value.
  • Greater-than (>) returns true if the right expression's value is larger then the left expression's value.
  • Less-than or equal to (<=) returns true if the right expression's value is smaller than or equal to the left expression's value.
  • Greater-than or equal to (>=) returns true if the right expression's value is larger than or equal to the left expression's value.
Examples:
; `i` will get true if a is greater then `b`.
i = a > b
; `i` will get true if a is equal to `b`.
i = a == b

Assignment Operators

= += -= *= /= %=

Assignment operators assign the value from the right Expression to the Variable (or Property) from the left expression. If the equal sign starts with one of the math operators, then the right expression's value will be added to the current value of the left expression, and then assigned to the left expression. Note that if the left expression is a property, then both the property's get and set functions will be called.

Examples:
; Assign 10 to `i`.
i = 10
; Add 1 to the current value of `i`, and assign it to `i`.
i += 1

Cast

as

The Casting operator attempts to cast the value from the left Expression to the type to the right of it, and returns the resulting value.

Examples:
; Cast a to a float and assign it to `i`.
i = a as float

Type Check

is

The Casting operator checks to see if the value from the left Expression is the type specified to the right of it, and returns true or false. If the type is a base type, like int, bool, or float, the check is strict and will only return true if the type exactly matches (a float is not an int, for example). If the type is an object type, then the check is loose and will return true if the object would be successfully cast to the type (an ObjectReference is a Form).

Examples:
; check to see if myObject is an ObjectReference
if myObject is ObjectReference
  Debug.Trace("I got an object reference!")
endIf

Statements

A statement is an arrangement of Expressions used to perform work (and may just be a simple expression). There are also some more complicated statements like "If" and "While".

Define Statement

<define statement> ::= <type> <identifier> ['=' <expression>]

A define statement defines a single Variable and, optionally, initializes it to a value. If a value is not given to it, it starts with the standard Default Value. A variable defined inside a function does not conflict with a variable defined in another function. A variable defined in an if or while block will not conflict with a variable defined in another if or while block that is not a child or a parent of the block that defined it.

Examples
; Create an integer variable named var that starts with the default value of 0.
int var
; Create a float variable name seconds that starts with a default value.
float seconds = CurrentTimeInMinutes() * 60.0f

Assign Statement

<assign statement> ::= (<l-value> '=' <expression>) |
                       (<l-value> '+=' <expression>) |
                       (<l-value> '-=' <expression>) |
                       (<l-value> '*=' <expression>) |
                       (<l-value> '/=' <expression>) |
                       (<l-value> '%=' <expression>)
<l-value>          ::= ([<expression> '.'] <identifier>)
                       (<expression> '[' <expression> ']')

An assignment statement calculates the results of the expression to the left of the assignment operator, and variable referred to by the l-value, and either assigns the result to the l-value, or modifies the l-value by the result.

Examples
; Assign 5 to x
x = 5
; Increment the property by the calculated value
MyObject.MyProperty += CoolFunction() * 10

Return Statement

'Return' [<expression>]

The return statement immediately stops running the function, calculates the result of the expression (if it exists), and returns it to the caller. The type the expression resolves to must match the return type of the function. If the function has no return type, then just use a return statement with no expression. If a function with a return type exits without a return statement, then none will be return. A warning will also be printed by the game if none is not allowed to be assigned to the return type.

Examples
; Assuming the return type is int, return the value to the caller.
return 5
; Return immediately, returning nothing
return
x = 5 ; This never runs, because execution has exited this function.

If Statement

<if statement> ::= 'If' <expression>
                     <statement>*
                   ['ElseIf' <expression>
                     <statement>*]*
                   ['Else'
                     <statement>*]
                   'EndIf'

The If statement calculates its expression and, if the result is true, runs the statements underneath it until it runs into an "ElseIf", "Else", or "EndIf". If the expression's result is false, then it jumps down the if, checking each "ElseIf" as well until it hits an "Else" (which then runs its statements) or an "EndIf". At each "ElseIf" encountered, it will check the expression and, if true, run the statements under it and if false, will jump to the next "ElseIf", "Else", or "EndIf".

Examples
; If `value` is true, will set x to 1, otherwise it will do nothing.
If (value)
  x = 1
EndIf
; If `myCoolValue` is 20, will set x to 5, otherwise it will set it to 10.
If (myCoolValue == 20)
  x = 5
Else
  x = 10
EndIf
; If the `value` is above 10 it will set x to 1,
; if it is below 10 it will set `x` to -1, otherwise it will set `x` to 0.
If (value > 10)
  x = 1
ElseIf (value < 10)
  x = -1
Else
  x = 0
EndIf

While Statement

'While' <expression>
  <statement>*
'EndWhile'

The while statement is a loop and will repeatedly execute the statements inside it until the expression is false.

Examples
; Loop until `x` is 10.
x = 0
While (x < 10)
  DoCoolStuff()
  x += 1
EndWhile

Expressions

<expression>       ::= <and expression> ('||' <and expression>)*
<and expression>   ::= <bool expression> ('&&' <bool expression>)*
<bool expression>  ::= <add expression> (<comparison operator> <add expression>)*
<add expression>   ::= <mult expression> (('+' | '-') <mult expression>)*
<mult expression>  ::= <unary expression> (('*' | '/' | '%') <unary expression>)*
<unary expression> ::= ['-' | '!'] <cast atom>
<cast atom>        ::= <dot atom> ['as' <type>]
<dot atom>         ::= (<array atom> ('.' <array func or id>)*) | <constant>
<array atom>       ::= <atom> ['[' <expression> ']']
<atom>             ::= ('(' <expression> ')') | ('new' <type> '[' <int> ']') | <func or id>
<array func or id> ::= <func or id> ['[' <expression> ']']
<func or id>       ::= <function call> | <scriptType> | 'length'

The basic expression is an arrangement of Operators, Function calls, and Variables chained together in such a way to do some work. These operations follow the Rules of Precedence to determine which gets executed first.

Examples
; Add 2 to the value of 4 times 10 (result is 42).
2 + 4 * 10
; Add 2 to 4, and multiply by 10 (result is 60).
(2 + 4) * 10
; Call a function and get a property from the results.
MyFunction().MyProperty
; Get the first element in the array returned by the function after a cast.
(MyVariable as MyObject).MyFunction()[0]

Namespaces

A script can now be put into a "namespace" which lets you organize scripts into sub-folders. Namespaces are designated by separating a series of identifiers with ':' characters. The namespace must match the path to the script file, relative to the root script folder.

For example, the script Scripts/Traps/Triggers/Tripwire.psc would be named Traps:Triggers:Tripwire. If the identifier refers to a script, it may have namespaces, separated by colons as follows.

<full script name> ::= <identifier> (':' <identifier>)*

The last identifier specifies the name of the script, and the identifiers in front of it are the namespaces. The namespaces are the folders that the script will be found in. For example, if you have a script in MyCoolStuff\Quests\MyQuest.psc the name of the script will be MyCoolStuff:Quests:MyQuest.

Clone this wiki locally