d2ast

package
v0.6.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2024 License: MPL-2.0 Imports: 13 Imported by: 8

Documentation

Overview

TODO: Remove boxes and cleanup like d2ir

d2ast implements the d2 language's abstract syntax tree.

Special characters to think about in parser: # """ ; [] {} | $ ' " \ : . -- <> * & ()

Index

Constants

This section is empty.

Variables

View Source
var BoardKeywords = map[string]struct{}{
	"layers":    {},
	"scenarios": {},
	"steps":     {},
}

BoardKeywords contains the keywords that create new boards.

View Source
var CompositeReservedKeywords = map[string]struct{}{
	"classes":    {},
	"constraint": {},
	"label":      {},
	"icon":       {},
}

CompositeReservedKeywords are reserved keywords that can hold composites

View Source
var FillPatterns = []string{
	"none",
	"dots",
	"lines",
	"grain",
	"paper",
}
View Source
var LabelPositions map[string]struct{}
View Source
var LabelPositionsArray = []string{
	"top-left",
	"top-center",
	"top-right",

	"center-left",
	"center-center",
	"center-right",

	"bottom-left",
	"bottom-center",
	"bottom-right",

	"outside-top-left",
	"outside-top-center",
	"outside-top-right",

	"outside-left-top",
	"outside-left-center",
	"outside-left-bottom",

	"outside-right-top",
	"outside-right-center",
	"outside-right-bottom",

	"outside-bottom-left",
	"outside-bottom-center",
	"outside-bottom-right",
}

LabelPositionsArray are the values that labels and icons can set `near` to

View Source
var LabelPositionsMapping = map[string]label.Position{
	"top-left":   label.InsideTopLeft,
	"top-center": label.InsideTopCenter,
	"top-right":  label.InsideTopRight,

	"center-left":   label.InsideMiddleLeft,
	"center-center": label.InsideMiddleCenter,
	"center-right":  label.InsideMiddleRight,

	"bottom-left":   label.InsideBottomLeft,
	"bottom-center": label.InsideBottomCenter,
	"bottom-right":  label.InsideBottomRight,

	"outside-top-left":   label.OutsideTopLeft,
	"outside-top-center": label.OutsideTopCenter,
	"outside-top-right":  label.OutsideTopRight,

	"outside-left-top":    label.OutsideLeftTop,
	"outside-left-center": label.OutsideLeftMiddle,
	"outside-left-bottom": label.OutsideLeftBottom,

	"outside-right-top":    label.OutsideRightTop,
	"outside-right-center": label.OutsideRightMiddle,
	"outside-right-bottom": label.OutsideRightBottom,

	"outside-bottom-left":   label.OutsideBottomLeft,
	"outside-bottom-center": label.OutsideBottomCenter,
	"outside-bottom-right":  label.OutsideBottomRight,
}
View Source
var NearConstants map[string]struct{}
View Source
var NearConstantsArray = []string{
	"top-left",
	"top-center",
	"top-right",

	"center-left",
	"center-right",

	"bottom-left",
	"bottom-center",
	"bottom-right",
}

TODO maybe autofmt should allow other values, and transform them to conform e.g. left-center becomes center-left

View Source
var ReservedKeywordHolders = map[string]struct{}{
	"style":            {},
	"source-arrowhead": {},
	"target-arrowhead": {},
}

ReservedKeywordHolders are reserved keywords that are meaningless on its own and must hold composites

View Source
var ReservedKeywords map[string]struct{}

All reserved keywords. See init below.

View Source
var SimpleReservedKeywords = map[string]struct{}{
	"label":          {},
	"desc":           {},
	"shape":          {},
	"icon":           {},
	"constraint":     {},
	"tooltip":        {},
	"link":           {},
	"near":           {},
	"width":          {},
	"height":         {},
	"direction":      {},
	"top":            {},
	"left":           {},
	"grid-rows":      {},
	"grid-columns":   {},
	"grid-gap":       {},
	"vertical-gap":   {},
	"horizontal-gap": {},
	"class":          {},
	"vars":           {},
}

Non Style/Holder keywords.

View Source
var StyleKeywords = map[string]struct{}{
	"opacity":       {},
	"stroke":        {},
	"fill":          {},
	"fill-pattern":  {},
	"stroke-width":  {},
	"stroke-dash":   {},
	"border-radius": {},

	"font":           {},
	"font-size":      {},
	"font-color":     {},
	"bold":           {},
	"italic":         {},
	"underline":      {},
	"text-transform": {},

	"shadow":        {},
	"multiple":      {},
	"double-border": {},

	"3d": {},

	"animated": {},
	"filled":   {},
}

StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword

View Source
var TextTransforms = []string{"none", "uppercase", "lowercase", "capitalize"}
View Source
var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@', '&'})

& is only special if it begins a key. - is only special if followed by another - in a key. ' " and | are only special if they begin an unquoted key or value.

View Source
var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'})

Functions

func IsDoubleGlob added in v0.6.1

func IsDoubleGlob(pattern []string) bool

func IsTripleGlob added in v0.6.1

func IsTripleGlob(pattern []string) bool

Types

type Array

type Array struct {
	Range Range          `json:"range"`
	Nodes []ArrayNodeBox `json:"nodes"`
}

func (*Array) GetRange

func (a *Array) GetRange() Range

func (*Array) Type

func (a *Array) Type() string

type ArrayNode

type ArrayNode interface {
	Node
	// contains filtered or unexported methods
}

ArrayNode is implemented by nodes that may be children of Arrays.

type ArrayNodeBox

type ArrayNodeBox struct {
	Comment            *Comment            `json:"comment,omitempty"`
	BlockComment       *BlockComment       `json:"block_comment,omitempty"`
	Substitution       *Substitution       `json:"substitution,omitempty"`
	Import             *Import             `json:"import,omitempty"`
	Null               *Null               `json:"null,omitempty"`
	Boolean            *Boolean            `json:"boolean,omitempty"`
	Number             *Number             `json:"number,omitempty"`
	UnquotedString     *UnquotedString     `json:"unquoted_string,omitempty"`
	DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"`
	SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"`
	BlockString        *BlockString        `json:"block_string,omitempty"`
	Array              *Array              `json:"array,omitempty"`
	Map                *Map                `json:"map,omitempty"`
}

ArrayNodeBox is used to box ArrayNode for JSON persistence.

func MakeArrayNodeBox added in v0.2.0

func MakeArrayNodeBox(an ArrayNode) ArrayNodeBox

func (ArrayNodeBox) Unbox

func (ab ArrayNodeBox) Unbox() ArrayNode

type BlockComment

type BlockComment struct {
	Range Range  `json:"range"`
	Value string `json:"value"`
}

func (*BlockComment) GetRange

func (c *BlockComment) GetRange() Range

func (*BlockComment) Type

func (c *BlockComment) Type() string

type BlockString

type BlockString struct {
	Range Range `json:"range"`

	// Quote contains the pipe delimiter for the block string.
	// e.g. if 5 pipes were used to begin a block string, then Quote == "||||".
	// The tag is not included.
	Quote string `json:"quote"`
	Tag   string `json:"tag"`
	Value string `json:"value"`
}

func (*BlockString) Copy

func (s *BlockString) Copy() String

func (*BlockString) GetRange

func (s *BlockString) GetRange() Range

func (*BlockString) ScalarString

func (s *BlockString) ScalarString() string

func (*BlockString) SetString

func (s *BlockString) SetString(s2 string)

func (*BlockString) Type

func (s *BlockString) Type() string

type Boolean

type Boolean struct {
	Range Range `json:"range"`
	Value bool  `json:"value"`
}

func (*Boolean) GetRange

func (b *Boolean) GetRange() Range

func (*Boolean) ScalarString

func (b *Boolean) ScalarString() string

func (*Boolean) Type

func (b *Boolean) Type() string

type Comment

type Comment struct {
	Range Range  `json:"range"`
	Value string `json:"value"`
}

func (*Comment) GetRange

func (c *Comment) GetRange() Range

func (*Comment) Type

func (c *Comment) Type() string

type DoubleQuotedString

type DoubleQuotedString struct {
	Range Range              `json:"range"`
	Value []InterpolationBox `json:"value"`
}

func FlatDoubleQuotedString

func FlatDoubleQuotedString(s string) *DoubleQuotedString

func (*DoubleQuotedString) Coalesce added in v0.6.0

func (s *DoubleQuotedString) Coalesce()

func (*DoubleQuotedString) Copy

func (s *DoubleQuotedString) Copy() String

func (*DoubleQuotedString) GetRange

func (s *DoubleQuotedString) GetRange() Range

func (*DoubleQuotedString) ScalarString

func (s *DoubleQuotedString) ScalarString() string

func (*DoubleQuotedString) SetString

func (s *DoubleQuotedString) SetString(s2 string)

func (*DoubleQuotedString) Type

func (s *DoubleQuotedString) Type() string

type Edge

type Edge struct {
	Range Range `json:"range"`

	Src *KeyPath `json:"src"`
	// empty, < or *
	SrcArrow string `json:"src_arrow"`

	Dst *KeyPath `json:"dst"`
	// empty, > or *
	DstArrow string `json:"dst_arrow"`
}

func (*Edge) Equals added in v0.6.1

func (e1 *Edge) Equals(e2 *Edge) bool

func (*Edge) GetRange

func (e *Edge) GetRange() Range

func (*Edge) Type

func (e *Edge) Type() string

type EdgeIndex

type EdgeIndex struct {
	Range Range `json:"range"`

	// [n] or [*]
	Int  *int `json:"int"`
	Glob bool `json:"glob"`
}

func (*EdgeIndex) Equals added in v0.6.1

func (ei1 *EdgeIndex) Equals(ei2 *EdgeIndex) bool

func (*EdgeIndex) GetRange

func (i *EdgeIndex) GetRange() Range

func (*EdgeIndex) Type

func (i *EdgeIndex) Type() string

type Error

type Error struct {
	Range   Range  `json:"range"`
	Message string `json:"errmsg"`
}

TODO: Right now this is here to be available in both the Parser and Compiler but eventually we should make this a real part of the AST so that autofmt works on files with parse errors and semantically it makes more sense. Compile would continue to maintain a separate set of errors and then we'd do a merge & sort to get the final list of errors for user display.

func (Error) Error

func (e Error) Error() string

type Import added in v0.5.0

type Import struct {
	Range Range `json:"range"`

	Spread bool         `json:"spread"`
	Pre    string       `json:"pre"`
	Path   []*StringBox `json:"path"`
}

func (*Import) Dir added in v0.6.7

func (i *Import) Dir() string

func (*Import) GetRange added in v0.5.0

func (i *Import) GetRange() Range

func (*Import) IDA added in v0.5.0

func (i *Import) IDA() (ida []string)

func (*Import) PathWithPre added in v0.5.0

func (i *Import) PathWithPre() string

func (*Import) Type added in v0.5.0

func (i *Import) Type() string

type InterpolationBox

type InterpolationBox struct {
	String       *string       `json:"string,omitempty"`
	StringRaw    *string       `json:"raw_string,omitempty"`
	Substitution *Substitution `json:"substitution,omitempty"`
}

InterpolationBox is used to select between strings and substitutions in unquoted and double quoted strings. There is no corresponding interface to avoid unnecessary abstraction.

type Key

type Key struct {
	Range Range `json:"range"`

	// Indicates this MapKey is a filter selector.
	Ampersand bool `json:"ampersand,omitempty"`

	// Indicates this MapKey is a not filter selector.
	NotAmpersand bool `json:"not_ampersand,omitempty"`

	// At least one of Key and Edges will be set but all four can also be set.
	// The following are all valid MapKeys:
	// Key:
	//   x
	// Edges:
	//   x -> y
	// Edges and EdgeIndex:
	//   (x -> y)[*]
	// Edges and EdgeKey:
	//   (x -> y).label
	// Key and Edges:
	//   container.(x -> y)
	// Key, Edges and EdgeKey:
	//   container.(x -> y -> z).label
	// Key, Edges, EdgeIndex EdgeKey:
	//   container.(x -> y -> z)[4].label
	Key       *KeyPath   `json:"key,omitempty"`
	Edges     []*Edge    `json:"edges,omitempty"`
	EdgeIndex *EdgeIndex `json:"edge_index,omitempty"`
	EdgeKey   *KeyPath   `json:"edge_key,omitempty"`

	Primary ScalarBox `json:"primary,omitempty"`
	Value   ValueBox  `json:"value"`
}

TODO: require @ on import values for readability

func (*Key) Copy added in v0.6.1

func (mk *Key) Copy() *Key

func (*Key) D2OracleEquals added in v0.6.1

func (mk1 *Key) D2OracleEquals(mk2 *Key) bool

func (*Key) Equals

func (mk1 *Key) Equals(mk2 *Key) bool

func (*Key) GetRange

func (k *Key) GetRange() Range

func (*Key) HasGlob added in v0.6.1

func (mk *Key) HasGlob() bool

func (*Key) HasTripleGlob added in v0.6.1

func (mk *Key) HasTripleGlob() bool

func (*Key) SetScalar

func (mk *Key) SetScalar(scalar ScalarBox)

func (*Key) SupportsGlobFilters added in v0.6.1

func (mk *Key) SupportsGlobFilters() bool

func (*Key) Type

func (k *Key) Type() string

type KeyPath

type KeyPath struct {
	Range Range        `json:"range"`
	Path  []*StringBox `json:"path"`
}

func MakeKeyPath added in v0.2.0

func MakeKeyPath(a []string) *KeyPath

func (*KeyPath) Copy added in v0.6.0

func (kp *KeyPath) Copy() *KeyPath

func (*KeyPath) Equals added in v0.6.1

func (kp1 *KeyPath) Equals(kp2 *KeyPath) bool

func (*KeyPath) FirstGlob added in v0.6.1

func (kp *KeyPath) FirstGlob() int

func (*KeyPath) GetRange

func (k *KeyPath) GetRange() Range

func (*KeyPath) HasGlob added in v0.6.0

func (kp *KeyPath) HasGlob() bool

func (*KeyPath) HasMultiGlob added in v0.6.1

func (kp *KeyPath) HasMultiGlob() bool

func (*KeyPath) HasTripleGlob added in v0.6.1

func (kp *KeyPath) HasTripleGlob() bool

func (*KeyPath) IDA added in v0.2.0

func (kp *KeyPath) IDA() (ida []string)

func (*KeyPath) Last added in v0.6.1

func (kp *KeyPath) Last() *StringBox

func (*KeyPath) Type

func (k *KeyPath) Type() string

type Map

type Map struct {
	Range Range        `json:"range"`
	Nodes []MapNodeBox `json:"nodes"`
}

func (*Map) GetRange

func (m *Map) GetRange() Range

func (*Map) HasFilter added in v0.6.1

func (m *Map) HasFilter() bool

func (*Map) InsertAfter

func (m *Map) InsertAfter(cursor, n MapNode)

func (*Map) InsertBefore

func (m *Map) InsertBefore(cursor, n MapNode)

func (*Map) IsFileMap

func (m *Map) IsFileMap() bool

func (*Map) Type

func (m *Map) Type() string

type MapNode

type MapNode interface {
	Node
	// contains filtered or unexported methods
}

MapNode is implemented by nodes that may be children of Maps.

type MapNodeBox

type MapNodeBox struct {
	Comment      *Comment      `json:"comment,omitempty"`
	BlockComment *BlockComment `json:"block_comment,omitempty"`
	Substitution *Substitution `json:"substitution,omitempty"`
	Import       *Import       `json:"import,omitempty"`
	MapKey       *Key          `json:"map_key,omitempty"`
}

MapNodeBox is used to box MapNode for JSON persistence.

func MakeMapNodeBox

func MakeMapNodeBox(n MapNode) MapNodeBox

func (MapNodeBox) IsBoardNode added in v0.6.0

func (mb MapNodeBox) IsBoardNode() bool

func (MapNodeBox) Unbox

func (mb MapNodeBox) Unbox() MapNode

type Node

type Node interface {

	// Type returns the user friendly name of the node.
	Type() string

	// GetRange returns the range a node occupies in its file.
	GetRange() Range
	// contains filtered or unexported methods
}

Node is the base interface implemented by all d2 AST nodes. TODO: add error node for autofmt of incomplete AST

type Null

type Null struct {
	Range Range `json:"range"`
}

func (*Null) GetRange

func (n *Null) GetRange() Range

func (*Null) ScalarString

func (n *Null) ScalarString() string

TODO: mistake, move into parse.go

func (*Null) Type

func (n *Null) Type() string

type Number

type Number struct {
	Range Range    `json:"range"`
	Raw   string   `json:"raw"`
	Value *big.Rat `json:"value"`
}

func (*Number) GetRange

func (n *Number) GetRange() Range

func (*Number) ScalarString

func (n *Number) ScalarString() string

func (*Number) Type

func (n *Number) Type() string

type Position

type Position struct {
	Line   int
	Column int
	Byte   int
}

Position represents a line:column and byte position in a file.

note: Line and Column are zero indexed. note: Column and Byte are UTF-8 byte indexes unless byUTF16 was passed to Position.Advance in . which they are UTF-16 code unit indexes. . If intended for Javascript consumption like in the browser or via LSP, byUTF16 is . set to true.

func (Position) Advance

func (p Position) Advance(r rune, byUTF16 bool) Position

Advance advances p's Line, Column and Byte by r and returns the new Position. Set byUTF16 to advance the position as though r represents a UTF-16 codepoint.

func (Position) AdvanceString added in v0.5.0

func (p Position) AdvanceString(s string, byUTF16 bool) Position

func (Position) Before added in v0.2.0

func (p Position) Before(p2 Position) bool

func (Position) Debug added in v0.5.0

func (p Position) Debug() string

func (*Position) From

func (p *Position) From(src *Position)

From copies src into p. It's used in the d2parser package to set a node's Range.End to the parser's current pos on all return paths with defer.

func (Position) MarshalText

func (p Position) MarshalText() ([]byte, error)

See docs on Range.

func (Position) String

func (p Position) String() string

String returns a line:column representation of the position suitable for error messages.

note: Should not normally be used directly, see Range.String()

func (Position) Subtract

func (p Position) Subtract(r rune, byUTF16 bool) Position

func (Position) SubtractString

func (p Position) SubtractString(s string, byUTF16 bool) Position

func (*Position) UnmarshalText

func (p *Position) UnmarshalText(b []byte) (err error)

See docs on Range.

type Range

type Range struct {
	Path  string
	Start Position
	End   Position
}

Range represents a range between Start and End in Path. It's also used in the d2parser package to represent the range of an error.

note: See docs on Position.

It has a custom JSON string encoding with encoding.TextMarshaler and encoding.TextUnmarshaler to keep it compact as the JSON struct encoding is too verbose, especially with json.MarshalIndent.

It looks like path,start-end

func MakeRange

func MakeRange(s string) Range

func (Range) Before added in v0.2.0

func (r Range) Before(r2 Range) bool

func (Range) MarshalText

func (r Range) MarshalText() ([]byte, error)

See docs on Range.

func (Range) OneLine

func (r Range) OneLine() bool

OneLine returns true if the Range starts and ends on the same line.

func (Range) String

func (r Range) String() string

String returns a string representation of the range including only the path and start.

If path is empty, it will be omitted.

The format is path:start

func (*Range) UnmarshalText

func (r *Range) UnmarshalText(b []byte) (err error)

See docs on Range.

type Scalar

type Scalar interface {
	Value

	ScalarString() string
	// contains filtered or unexported methods
}

Scalar is implemented by nodes that represent scalar values.

type ScalarBox

type ScalarBox struct {
	Null               *Null               `json:"null,omitempty"`
	Boolean            *Boolean            `json:"boolean,omitempty"`
	Number             *Number             `json:"number,omitempty"`
	UnquotedString     *UnquotedString     `json:"unquoted_string,omitempty"`
	DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"`
	SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"`
	BlockString        *BlockString        `json:"block_string,omitempty"`
}

ScalarBox is used to box Scalar for JSON persistence. TODO: implement ScalarString()

func (ScalarBox) ScalarString added in v0.6.1

func (sb ScalarBox) ScalarString() string

func (ScalarBox) Unbox

func (sb ScalarBox) Unbox() Scalar

type SingleQuotedString

type SingleQuotedString struct {
	Range Range  `json:"range"`
	Raw   string `json:"raw"`
	Value string `json:"value"`
}

func (*SingleQuotedString) Copy

func (s *SingleQuotedString) Copy() String

func (*SingleQuotedString) GetRange

func (s *SingleQuotedString) GetRange() Range

func (*SingleQuotedString) ScalarString

func (s *SingleQuotedString) ScalarString() string

func (*SingleQuotedString) SetString

func (s *SingleQuotedString) SetString(s2 string)

func (*SingleQuotedString) Type

func (s *SingleQuotedString) Type() string

type String

type String interface {
	Scalar
	SetString(string)
	Copy() String
	// contains filtered or unexported methods
}

String is implemented by nodes that represent strings.

func RawString

func RawString(s string, inKey bool) String

RawString returns s in a AST String node that can format s in the most aesthetically pleasing way.

type StringBox

type StringBox struct {
	UnquotedString     *UnquotedString     `json:"unquoted_string,omitempty"`
	DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"`
	SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"`
	BlockString        *BlockString        `json:"block_string,omitempty"`
}

StringBox is used to box String for JSON persistence.

func RawStringBox added in v0.5.0

func RawStringBox(s string, inKey bool) *StringBox

func (*StringBox) ScalarString added in v0.6.0

func (sb *StringBox) ScalarString() string

func (*StringBox) Unbox

func (sb *StringBox) Unbox() String

type Substitution

type Substitution struct {
	Range Range `json:"range"`

	Spread bool         `json:"spread"`
	Path   []*StringBox `json:"path"`
}

func (*Substitution) GetRange

func (s *Substitution) GetRange() Range

func (*Substitution) IDA added in v0.5.0

func (s *Substitution) IDA() (ida []string)

func (*Substitution) Type

func (s *Substitution) Type() string

type UnquotedString

type UnquotedString struct {
	Range Range              `json:"range"`
	Value []InterpolationBox `json:"value"`
	// Pattern holds the parsed glob pattern if in a key and the unquoted string represents a valid pattern.
	Pattern []string `json:"pattern,omitempty"`
}

func FlatUnquotedString

func FlatUnquotedString(s string) *UnquotedString

func (*UnquotedString) Coalesce added in v0.6.0

func (s *UnquotedString) Coalesce()

func (*UnquotedString) Copy

func (s *UnquotedString) Copy() String

func (*UnquotedString) GetRange

func (s *UnquotedString) GetRange() Range

func (*UnquotedString) ScalarString

func (s *UnquotedString) ScalarString() string

func (*UnquotedString) SetString

func (s *UnquotedString) SetString(s2 string)

func (*UnquotedString) Type

func (s *UnquotedString) Type() string

type Value

type Value interface {
	ArrayNode
	// contains filtered or unexported methods
}

Value is implemented by nodes that may be values of a key.

type ValueBox

type ValueBox struct {
	Null               *Null               `json:"null,omitempty"`
	Boolean            *Boolean            `json:"boolean,omitempty"`
	Number             *Number             `json:"number,omitempty"`
	UnquotedString     *UnquotedString     `json:"unquoted_string,omitempty"`
	DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"`
	SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"`
	BlockString        *BlockString        `json:"block_string,omitempty"`
	Array              *Array              `json:"array,omitempty"`
	Map                *Map                `json:"map,omitempty"`
	Import             *Import             `json:"import,omitempty"`
}

ValueBox is used to box Value for JSON persistence.

func MakeValueBox

func MakeValueBox(v Value) ValueBox

func (ValueBox) ScalarBox

func (vb ValueBox) ScalarBox() ScalarBox

func (ValueBox) StringBox

func (vb ValueBox) StringBox() *StringBox

func (ValueBox) Unbox

func (vb ValueBox) Unbox() Value

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL