gendsl

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 2, 2024 License: Apache-2.0 Imports: 7 Imported by: 0

README

gendsl

gendsl provides a framework to create a DSL in Lisp style and allows you to customize your own expressions so that you can integrate it into your own application without accessing any lexer or parser.

✨ Features

  • ⌨️ Highly customizable: Inject your own golang functions in the DSL and actually define your own expressions like 'if-then-else', 'switch-case'.
  • 🔌 Easy and lightweight: Syntax with simplicity and explicity, easy to learn.
  • 🌐 Option value: Option value syntax like Racket for data description.
  • 🎯 Value injection: Access and inject any variables/functions in your DSL.
  • 🔎 Lexical scope: Variable name reference are lexical-scoped.

📦 Installation

go get github.com/ccbhj/gendsl

📋 Usage

Setup environment

An environment is basically a table to lookup identifiers. By injecting values into an environment and pass the environment when evaluating expression, you can access values by its name in your expressions. Here are how you declare and inject value into an environment:

env := gendsl.NewEnv().
         WithInt("ONE", 1).                           // inject an integer 1 named ONE
         WithString("@TWO", "2").                     // inject an string 2 named @TWO
         WithProcedure("PRINTLN", gendsl.Procedure{   // inject an procedure named PRINTLN with one or more arguments
             Eval: gendsl.CheckNArgs("+", printlnOp),
         }).
         WithProcedure("PLUS", gendsl.Procedure{   
             Eval: gendsl.CheckNArgs("2", plusOp),
         })

That's it, now you have an environment for expression evaluation. Note that values used in our expression are typed. Currently we support Int/Uint/Bool/String/Float/UserData/Nil/Procedure. If you cannot find any type that can satisfy your need, use UserData, and use Nil instead of nil literal as possible as you can.

Evaluate expressions

With EvalExpr(expr string, env *Env) (Value, error) you can evaluate an expression into a value. The expression syntax is the same as the parenthesized syntax of Lisp which means that an expression is either an value X or parenthesized list (X Y Z ...) where X is considered as a procedure and Y, Z ... are its arguments. With the env we defined before, we can write expressions like:

"HELLO WORLD"         ; => String("HELLO WORLD")
100                   ; => Int(100)
100.0                 ; => Float(100)
100u                  ; => Uint(100)
#t                    ; => Bool(true)
nil                   ; => Nil
(PRINTLN 10)          ; => 10
(PRINTLN (PLUS 1 2))  ; => 3
(PRINTLN (MINUS 1 2)) ; => error!!! since 'MINUS' is not defined in env.
(PRINTLN :out "stderr" (PLUS 1 2)) ; => 3, output to the stderr
Define procedures
Basic

A procedure is just a simple function that accept a bunch of expressions and some options then return a value.

_plus := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
    var ret gendsl.Int
    for _, arg := range args {
        v, err := arg.Eval()            // evaluate arguments.
        if err != nil {
            return nil, err
        }
        if v.Type() == gendsl.ValueTypeInt {
            ret += v.(gendsl.Int)
        }
    }

    return ret, nil
}

env := gendsl.NewEnv().WithProcedure("PLUS", gendsl.Procedure{   
    Eval: gendsl.CheckNArgs("+", _plus),
})

The EvalCtx provides some information for evaluation including the env of the outer scope, and args are some expressions as arguments. Inject your function wrapped by gendsl.Procedure into an env then you are good to go use it in your expressions. You may want to use CheckNArgs() to save you from checking the amount of arguments everywhere.

Control the evaluation of an expression

By calling arg.Eval(), we can evaluate the sub-expression for this procedure. This means that the sub-expression(or the sub-ast) is not evaluated until we call the Eval() method. With this ability, you can define your own 'if-else-then' like this:

// _if takes first argument C as condition,
// returns the second expression if C is not nil, and returns execute the third expression otherwise
// example: 
//   (IF (EQUAL 1 1) "foo" "bar") ; => "foo"
// Note that expression "bar" will not be evaluated.
_if := func(_ *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
    cond, err := args[0].Eval()
    if err != nil {
        return nil, err
    }
    if cond.Type() != gendsl.ValueTypeNil { // not-nil is considered as true
        return args[1].Eval()
    } else {
        return args[2].Eval()
    }
}

Also, you can inject values during a procedure's evaluation by arg.EvalWithEnv(e) so that you can have something like 'package' scope:

// _block lets the procedure PLUS can only be visible inside expressions of the procedure BLOCK.
// example:
//   (BLOCK 1 (PLUS 2 3)) ; => Int(5)
//   (PLUS 2 3)           ; => error! PLUS is not defined outside BLOCK
_block := func(_ *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
    localEnv := gendsl.NewEnv().
        WithProcedure("PLUS", gendsl.Procedure{Eval: plusOp})

    var ret gendsl.Value
    for i, arg := range args {
        v, err := arg.EvalWithEnv(localEnv)
        if err != nil {
            return nil, err
        }
        ret = v
    }

    return v, nil
}

See the ExampleEvalExpr for a more detailed example that defines and output a JSON.

Access input data in procedures

You might want to access some data that used as the input of your expression across all the procedures. Use EvalExprWithData(expr string, env *Env, data any) (Value, error) pass the data before evaluation and then access it by reading the ectx.UserData.

printlnOp := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
    output := ectx.UserData.(*os.File)
    for _, arg := range args {
        v, err := arg.Eval()
        if err != nil {
            return nil, err
        }
        fmt.Fprintln(output, v.Unwrap())
    }
    return gendsl.Nil{}, nil
}

// print "helloworld" to stderr
_, err := gendsl.EvalExprWithData(`(PRINTLN "helloworld")`,
    gendsl.NewEnv().WithProcedure("PRINTLN", gendsl.Procedure{
        Eval: gendsl.CheckNArgs("+", printlnOp),
    }),
    os.Stderr,
)

See ExampleEvalExprWithData for a more detailed example of a mini awk.

Use option value for data declaration

We also support :#option {value} for simple data declaration where {value} can only be a simple litreal. You can also use it to control the behavior of a procedure.

printlnOp := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
    output := os.Stdout
    outputOpt := options["out"]
    switch outputOpt.Unwrap().(string) {
    case "stdout":
        output = os.Stdout
    case "stderr":
        output = os.Stderr
    }
    for _, arg := range args {
        v, err := arg.Eval()
        if err != nil {
            return nil, err
        }
        fmt.Fprintln(output, v.Unwrap())
    }
    return gendsl.Nil{}, nil
}

// print "helloworld" to stderr
_, err := gendsl.EvalExprWithData(`(PRINTLN #:out "stderr"  "helloworld")`,
    gendsl.NewEnv().WithProcedure("PRINTLN", gendsl.Procedure{
        Eval: gendsl.CheckNArgs("+", printlnOp),
    }),
    nil,
)

🛠️ Syntax

The syntax is pretty simple since everything is just nothing more that an expression which produces a value.

Expression

Our DSL can only be an single expression(for now):

DSL        = Expression
Expression = Int 
           | Uint 
           | Float 
           | Boolean
           | String
           | Nil
           | Identifier
           | '(' Identifier Options? Expression... ')'

Here are some examples:

> 1                ; => integer 1
> $1               ; => any thing named "$1"
> (PLUS 1 1)       ; => Invoke procedure named PLUS
> (PLUS #:N 2 1 1)  ; => Invoke procedure named PLUS with some options

Noted that options can only be declared before any argument.

Comment

Just like Common-Lisp, Scheme and Clojure, anything following ';' are treated as comments.

> 1                ; This is a comment
Literal Data Types

We support these types of data and they can be Unwrap() into Go value.

Type Go Type
int ValueTypeInt int64
uint ValueTypeUint uint64
string ValueTypeString string
float ValueTypeFloat float64
bool ValueTypeBool bool
nil ValueType nil
any ValueTypeUserData any
procedure ValueTypeProcedure ProcedureFn
numbers(Int/Uint/Float)
Int                    = [+-]? IntegerLiteral
IntegerLiteral         = '0x' HexDigit+
                       | '0X' HexDigit+
                       | DecimalDigit+

UnsignedIntegerLiteral = IntegerLiteral 'u'

FloatLiteral           = [+-]? DecimalDigit+ '.' DecimalDigit+? Exponent?
                       | [+-]? DecimalDigit+ Exponent
                       | [+-]? '.' DecimalDigit+ Exponent?
Exponent               =  [eE]? [+-]? DecimalDigit+

These expressions are parsed as numbers:

> 1              ; Int(1)
> -1             ; Int(-1)
> 0x10           ; Int(16)
> 0x10u          ; Uint(16)
> 0X10u          ; Uint(16)
> 0.             ; Float(0)
> .25            ; Float(0.25)
> 1.1            ; Float(1.1)
> 1.e+0          ; Float(1.0)
> 1.1e+0         ; Float(1.1)
> 1.1e-1         ; Float(0.11)
> 1E6            ; Float(1000000)
> 0.15e2         ; Float(15)
String
LongString = '"""' (![\"] .)* '"""'
String     = '"' Char+ '"'
Char       = '\u' HexDigit HexDigit HexDigit HexDigit
           | '\U' HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit
           | '\x' HexDigit HexDigit
           | '\' [abfnrtv\"']
           | .*

These expressions are parsed as a string, long string is supported as well:

> "'"                    ; String(`'`)
> "\\"                   ; String(`\`)
> "\u65e5"               ; String("日")
> "\U00008a9e"           ; String("语")
> "\u65e5本\U00008a9e"   ; String("日本语")
> "\x61"                 ; String("a")
> """x61"""              ; String("x61")
> """\\"""               ; String(`\\`)
> """\u65e5"""           ; String(`\u65e5`)
> """line                ; String("line\nbreak")
break"""                 
Bool
BoolLiteral = '#' [t/f]

These expressions are parsed as a bool:

> #t      ; true
> #f      ; false
Nil
NilLiteral = 'nil'

nil represents nil in Go:

> nil

Noted that injecting a variable called 'nil' makes no sense, and you will get a 'nil' value instead of an identifier.

Identifiers
Identifier = [a-zA-Z@$?_] (LetterOrDigit / '-' / '?')*

These expressions are parsed as identifiers:

> foo
> @bar
> ?hello
> _world
> foo-bar
> a0
> a_0
> is-string?

💡 Examples

Swith case expression
type Case struct {
    Cond gendsl.Expr
    Then gendsl.Expr
}
_case := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, _ map[string]gendsl.Value) (gendsl.Value, error) {
    return &gendsl.UserData{V: Case{args[0], args[1]}}, nil
}

_switch := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, _ map[string]gendsl.Value) (gendsl.Value, error) {
    var ret gendsl.Value
    env := ectx.Env().Clone().WithProcedure("CASE", gendsl.Procedure{
        Eval: gendsl.CheckNArgs("2", _case),
    })
    expect, err := args[0].Eval()
    if err != nil {
        return nil, err
    }
    for _, arg := range args[1:] {
        cv, err := arg.EvalWithEnv(env)
        if err != nil {
            return nil, err
        }
        c, ok := cv.Unwrap().(Case)
        if !ok {
            panic("expecting a cas ")
        }

        cond, err := c.Cond.Eval()
        if err != nil {
            return nil, err
        }
        if cond == expect {
            v, err := c.Then.Eval()
            if err != nil {
                return nil, err
            }
            ret = v
        }
    }

    return ret, nil
}

script := `
(SWITCH "FOO"
(CASE "BAR" "no")
(CASE "FOO" "yes")
)
`

val, err := gendsl.EvalExprWithData(script,
    gendsl.NewEnv().
        WithProcedure("SWITCH", gendsl.Procedure{
            Eval: gendsl.CheckNArgs("+", _switch),
        }),
    nil,
)

if err != nil {
    panic(err)
}
println(val.Unwrap().(string))
// output: yes

Local variable injection
func _let(_ *gendsl.EvalCtx, args []gendsl.Expr, options map[string]gendsl.Value) (gendsl.Value, error) {
	name, err := args[0].Eval()
	if err != nil {
		return nil, err
	}
	val, err := args[1].Eval()
	if err != nil {
		return nil, err
	}

	return args[2].EvalWithEnv(gendsl.NewEnv().WithValue(string(name.(gendsl.String)), val))
}

script := `
(LET "foo" 10
    (PRINTLN foo)
)
`
env := gendsl.NewEnv().
        WithProcedure("let", gendsl.Procedure{Eval: gendsl.CheckNArgs("3", _let)}))
gendsl.EvalExpr(script, env) 
// output: 10

Documentation

Overview

Package gendsl provides framework a DSL in [Lisp](https://en.wikipedia.org/wiki/Lisp_(programming_language)) style and allows you to customize your own expressions so that you can integrate it into your own golang application without accessing any lexer or parser.

Index

Examples

Constants

View Source
const (
	ValueTypeInt       = 1 << iota // Int
	ValueTypeUInt                  // Uint
	ValueTypeString                // String
	ValueTypeBool                  // Bool
	ValueTypeFloat                 // Float
	ValueTypeProcedure             // Procedure
	ValueTypeUserData              // UserData
	ValueTypeNil                   // Nil
)

Variables

This section is empty.

Functions

func Pretty

func Pretty(pretty bool) func(*parser) error

func Size

func Size(size int) func(*parser) error

Types

type Bool

type Bool bool

func (Bool) Type

func (Bool) Type() ValueType

func (Bool) Unwrap

func (b Bool) Unwrap() any

type Env

type Env struct {
	// contains filtered or unexported fields
}

Env stores the mapping of identifiers and values. Note that an Env is not concurrently safe.

func NewEnv

func NewEnv() *Env

NewEnv creates a new Env.

func (*Env) Clone

func (e *Env) Clone() *Env

Clone deep copy a new env

func (*Env) Lookup

func (e *Env) Lookup(id string) (v Value, found bool)

Lookup looks up the value of `id`. `found` report whether an value could be found in the env.

func (*Env) WithBool

func (e *Env) WithBool(id string, b Bool) *Env

WithBool registers a gendsl.Bool into the env.

func (*Env) WithFloat

func (e *Env) WithFloat(id string, f Float) *Env

WithFloat registers a gendsl.Float into the env.

func (*Env) WithInt

func (e *Env) WithInt(id string, i Int) *Env

WithInt registers a gendsl.Int into the env.

func (*Env) WithNil

func (e *Env) WithNil(id string, n Nil) *Env

WithNil registers a gendsl.Nil into the env.

func (*Env) WithProcedure

func (e *Env) WithProcedure(id string, p Procedure) *Env

WithProcedure registers a gendsl.Procedure into the env.

func (*Env) WithString

func (e *Env) WithString(id string, s String) *Env

WithString registers a gendsl.String into the env.

func (*Env) WithUint

func (e *Env) WithUint(id string, u Uint) *Env

WithUint registers a gendsl.Uint into the env.

func (*Env) WithUserData

func (e *Env) WithUserData(id string, ud *UserData) *Env

WithUserData registers a gendsl.UserData into the env.

func (*Env) WithValue

func (e *Env) WithValue(id string, val Value) *Env

WithValue registers any gendsl.Value into the env, it will panic if val == nil.

type EvalCtx

type EvalCtx struct {
	UserData any // UserData that is used across the entire script evaluation
	// contains filtered or unexported fields
}

EvalCtx holds some information used for evaluation.

func NewEvalCtx

func NewEvalCtx(p *EvalCtx, userData any, env *Env) *EvalCtx

NewEvalCtx creates a new EvalCtx with `p` as the output scope EvalCtx(nil is allowed), `userData` is argument used across the whole evaluation, `env` as the env for current scope evaluation, nil is allowed here and an empty env will be created for it.

func (*EvalCtx) Derive

func (e *EvalCtx) Derive(newEnv *Env) *EvalCtx

func (*EvalCtx) Env

func (e *EvalCtx) Env() *Env

func (*EvalCtx) Lookup

func (e *EvalCtx) Lookup(id string) (any, bool)

Lookup looks up an identifier in the current env, and try to look it up in its outter scope recurssively if not found.

func (*EvalCtx) OutScopeEvalCtx

func (e *EvalCtx) OutScopeEvalCtx() *EvalCtx

OutScopeEvalCtx returns gendsl.EvalCtx from the outter scope.

type EvalOpt

type EvalOpt struct {
	Env *Env
}

EvalOpt for some options to control the evaluate behavior

type EvaluateError

type EvaluateError struct {
	// pos where the expression cannot be evaluated.
	BeginLine, EndLine int
	BeginSym, EndSym   int
	// contains filtered or unexported fields
}

EvaluateError got thrown during the evaluation.

func (*EvaluateError) Cause

func (s *EvaluateError) Cause() error

func (*EvaluateError) Error

func (s *EvaluateError) Error() string

func (*EvaluateError) Unwrap

func (s *EvaluateError) Unwrap() error

type Expr

type Expr func(EvalOpt) (Value, error)

Expr wraps the evalution of an ast node, or in another word, an expression, It allows you control when the evalution can be done, or the env for evalution, so that you can program your procedure to act like a macro.

func (Expr) Eval

func (e Expr) Eval() (Value, error)

Eval evaluate an gendsl.Expr, return the result of this expression.

These errors might be returned:

func (Expr) EvalWithEnv

func (e Expr) EvalWithEnv(env *Env) (Value, error)

EvalWithEnv evaluate an gendsl.Expr with a new env, return the result of this expression. We will lookup an identifier in `env` first, and we will look it up again in the parent env when its value is not found in `env`.

These errors might be returned:

type Float

type Float float64

func (Float) Type

func (Float) Type() ValueType

func (Float) Unwrap

func (f Float) Unwrap() any

type Int

type Int int64

func (Int) Type

func (Int) Type() ValueType

func (Int) Unwrap

func (i Int) Unwrap() any

type Nil

type Nil struct{}

Use Nil instead of nil literal to represent nil

func (Nil) String

func (Nil) String() string

func (Nil) Type

func (Nil) Type() ValueType

func (Nil) Unwrap

func (Nil) Unwrap() any

type ParseContext

type ParseContext struct {
	// contains filtered or unexported fields
}

ParseContext holds the stateless parser context for a compiled script. It can be reused and re-evaluated with different gendsl.EvalCtx.

func MakeParseContext

func MakeParseContext(expr string) (*ParseContext, error)

MakeParseContext parses the expr and compiles it into an ast. You can save the *ParseContext for later usage, since there is no side-affect during evaluation.

func (*ParseContext) Eval

func (c *ParseContext) Eval(evalCtx *EvalCtx) (Value, error)

Eval evaluates the compiled script with an evalCtx. It will panic if evalCtx is nil.

func (*ParseContext) PrintTree

func (c *ParseContext) PrintTree()

PrintTree output the syntax tree to the stdio

type Procedure

type Procedure struct {
	Eval ProcedureFn
}

Procedure define how an expression in the format of (X Y Z...) got evaluated.

func (Procedure) Type

func (o Procedure) Type() ValueType

func (Procedure) Unwrap

func (o Procedure) Unwrap() any

type ProcedureFn

type ProcedureFn func(evalCtx *EvalCtx, args []Expr, options map[string]Value) (Value, error)

ProcedureFn specify the behavior of an gendsl.Procedure. `evalCtx` carry some information that might be used during the evaluation, see gendsl.EvalCtx

func CheckNArgs

func CheckNArgs(nargs string, evalFn ProcedureFn) ProcedureFn

CheckNArgs check the amount of the operands for a procedure by wrapping an EvalFn.

`nargs` specify the pattern of the amount of operands:

  • "+" to accept one or more than one operands
  • "*" or "" to accept any amount of operands
  • "?" to accept no or one operand
  • "n" for whatever number the strconv.Atoi can accept, to check the exact amount of the operands

type String

type String string

func (String) Type

func (String) Type() ValueType

func (String) Unwrap

func (s String) Unwrap() any

type SyntaxError

type SyntaxError struct {
	// contains filtered or unexported fields
}

SyntaxError got thrown when a parsing error found.

func (*SyntaxError) Error

func (e *SyntaxError) Error() string

type Uint

type Uint uint64

func (Uint) Type

func (Uint) Type() ValueType

func (Uint) Unwrap

func (u Uint) Unwrap() any

type UnboundedIdentifierError

type UnboundedIdentifierError struct {
	ID string // ID that cannot be found
	// position where the unbounded id found
	BeginLine, EndLine int
	BeginSym, EndSym   int
}

UnboundedIdentifierError got thrown when an identifier cannot be found in env.

func (*UnboundedIdentifierError) Error

func (s *UnboundedIdentifierError) Error() string

type UserData

type UserData struct {
	V any
}

UserData wraps any value. You can use it when no type of Value can be used.

func (*UserData) Type

func (*UserData) Type() ValueType

func (*UserData) Unwrap

func (u *UserData) Unwrap() any

type Value

type Value interface {
	// Type return the ValueType of a Value.
	Type() ValueType
	// Unwrap convert Value to go value, so that it can be used with type conversion syntax.
	Unwrap() any
	// contains filtered or unexported methods
}

Value represent all values that are used in the DSL[gendsl.ValueType]

A Value can be converted to a go value by calling Unwrap(), the type mapping is as following:

  • Int -> int64
  • Uint -> uint64
  • String -> string
  • Float -> float64
  • Bool -> bool
  • Nil -> nil
  • Procedure -> EvalFn
  • UserData -> any

func EvalExpr

func EvalExpr(expr string, env *Env) (Value, error)

EvalExpr evaluates an `expr` with `env` and return a Value result which could be nil as the result.

Example
package main

import (
	"fmt"

	"github.com/ccbhj/gendsl"
)

func main() {
	plusOp := func(_ *gendsl.EvalCtx, args []gendsl.Expr, _ map[string]gendsl.Value) (gendsl.Value, error) {
		var ret gendsl.Int
		for _, arg := range args {
			v, err := arg.Eval()
			if err != nil {
				return nil, err
			}
			if v.Type() == gendsl.ValueTypeInt {
				ret += v.(gendsl.Int)
			}
		}

		return ret, nil
	}

	result, err := gendsl.EvalExpr("(PLUS ONE $TWO @THREE 4)",
		gendsl.NewEnv().WithInt("ONE", 1).
			WithInt("$TWO", 2).
			WithInt("@THREE", 3).
			WithProcedure("PLUS", gendsl.Procedure{
				Eval: gendsl.CheckNArgs("+", plusOp),
			}),
	)

	if err != nil {
		panic(err)
	}
	fmt.Println(result.Unwrap())
}
Output:

10

func EvalExprWithData

func EvalExprWithData(expr string, env *Env, data any) (Value, error)

EvalExprWithData evaluates an `expr` with `env`, and allow you to pass a data to use across the entrie evaluation.

Example
package main

import (
	"fmt"
	"os"

	"github.com/ccbhj/gendsl"
)

func main() {
	printlnOp := func(ectx *gendsl.EvalCtx, args []gendsl.Expr, _ map[string]gendsl.Value) (gendsl.Value, error) {
		output := ectx.UserData.(*os.File)
		for _, arg := range args {
			v, err := arg.Eval()
			if err != nil {
				return nil, err
			}
			fmt.Fprintln(output, v.Unwrap())
		}
		return gendsl.Nil{}, nil
	}

	out := os.Stdout
	_, err := gendsl.EvalExprWithData("(PRINTLN ONE $TWO @THREE 4)",
		gendsl.NewEnv().WithInt("ONE", 1).
			WithInt("$TWO", 2).
			WithInt("@THREE", 3).
			WithProcedure("PRINTLN", gendsl.Procedure{
				Eval: gendsl.CheckNArgs("+", printlnOp),
			}),
		out,
	)

	if err != nil {
		panic(err)
	}
}
Output:

1
2
3
4

type ValueType

type ValueType uint64

ValueType specify the type of an Value

func (ValueType) String

func (v ValueType) String() string

Directories

Path Synopsis
cmd
echo
package main proveides a cmd that reads an expression from input or file and print the result or pretty print the ast for debugging.
package main proveides a cmd that reads an expression from input or file and print the result or pretty print the ast for debugging.

Jump to

Keyboard shortcuts

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