package goja

import (
	
	
	

	
	
	
)

type blockType int

const (
	blockLoop blockType = iota
	blockLoopEnum
	blockTry
	blockLabel
	blockSwitch
	blockWith
	blockScope
	blockIterScope
	blockOptChain
)

const (
	maskConst     = 1 << 31
	maskVar       = 1 << 30
	maskDeletable = 1 << 29
	maskStrict    = maskDeletable

	maskTyp = maskConst | maskVar | maskDeletable
)

type varType byte

const (
	varTypeVar varType = iota
	varTypeLet
	varTypeStrictConst
	varTypeConst
)

const thisBindingName = " this" // must not be a valid identifier

type CompilerError struct {
	Message string
	File    *file.File
	Offset  int
}

type CompilerSyntaxError struct {
	CompilerError
}

type CompilerReferenceError struct {
	CompilerError
}

type srcMapItem struct {
	pc     int
	srcPos int
}

// Program is an internal, compiled representation of code which is produced by the Compile function.
// This representation is not linked to a runtime in any way and can be used concurrently.
// It is always preferable to use a Program over a string when running code as it skips the compilation step.
type Program struct {
	code []instruction

	funcName unistring.String
	src      *file.File
	srcMap   []srcMapItem
}

type compiler struct {
	p     *Program
	scope *scope
	block *block

	classScope *classScope

	enumGetExpr compiledEnumGetExpr

	evalVM *vm // VM used to evaluate constant expressions
	ctxVM  *vm // VM in which an eval() code is compiled

	codeScratchpad []instruction

	stringCache map[unistring.String]Value
}

type binding struct {
	scope        *scope
	name         unistring.String
	accessPoints map[*scope]*[]int
	isConst      bool
	isStrict     bool
	isArg        bool
	isVar        bool
	inStash      bool
}

func ( *binding) ( *scope) *[]int {
	 := .accessPoints[]
	if  == nil {
		 := make([]int, 0, 1)
		 = &
		if .accessPoints == nil {
			.accessPoints = make(map[*scope]*[]int)
		}
		.accessPoints[] = 
	}
	return 
}

func ( *binding) ( int) {
	 := .scope.c.scope
	 := .getAccessPointsForScope()
	* = append(*, -.base)
}

func ( *binding) ( *scope,  int) {
	 := .getAccessPointsForScope()
	* = append(*, -.base)
}

func ( *binding) () {
	 := .scope.c.scope
	 := .getAccessPointsForScope()
	* = append(*, len(.prg.code)-.base)
}

func ( *binding) () {
	.markAccessPoint()
	if .isVar && !.isArg {
		.scope.c.emit(loadStack(0))
	} else {
		.scope.c.emit(loadStackLex(0))
	}
}

func ( *binding) ( int) {
	.markAccessPointAt()
	if .isVar && !.isArg {
		.scope.c.p.code[] = loadStack(0)
	} else {
		.scope.c.p.code[] = loadStackLex(0)
	}
}

func ( *binding) () {
	if .isVar && !.isArg {
		// no-op
	} else {
		// make sure TDZ is checked
		.markAccessPoint()
		.scope.c.emit(loadStackLex(0), pop)
	}
}

func ( *binding) () {
	if .isConst {
		if .isStrict || .scope.c.scope.strict {
			.scope.c.emit(throwAssignToConst)
		}
		return
	}
	.markAccessPoint()
	if .isVar && !.isArg {
		.scope.c.emit(storeStack(0))
	} else {
		.scope.c.emit(storeStackLex(0))
	}
}

func ( *binding) () {
	if .isConst {
		if .isStrict || .scope.c.scope.strict {
			.scope.c.emit(throwAssignToConst)
		}
		return
	}
	.markAccessPoint()
	if .isVar && !.isArg {
		.scope.c.emit(storeStackP(0))
	} else {
		.scope.c.emit(storeStackLexP(0))
	}
}

func ( *binding) () {
	if !.isVar && .scope.outer == nil {
		.scope.c.emit(initGlobalP(.name))
	} else {
		.markAccessPoint()
		.scope.c.emit(initStackP(0))
	}
}

func ( *binding) () {
	if !.isVar && .scope.outer == nil {
		.scope.c.emit(initGlobal(.name))
	} else {
		.markAccessPoint()
		.scope.c.emit(initStack(0))
	}
}

func ( *binding) ( int) {
	if !.isVar && .scope.outer == nil {
		.scope.c.p.code[] = initGlobal(.name)
	} else {
		.markAccessPointAt()
		.scope.c.p.code[] = initStack(0)
	}
}

func ( *binding) ( *scope,  int) {
	if !.isVar && .outer == nil {
		.c.p.code[] = initGlobal(.name)
	} else {
		.markAccessPointAtScope(, )
		.c.p.code[] = initStack(0)
	}
}

func ( *binding) ( *scope,  int) {
	if !.isVar && .outer == nil {
		.c.p.code[] = initGlobalP(.name)
	} else {
		.markAccessPointAtScope(, )
		.c.p.code[] = initStackP(0)
	}
}

func ( *binding) ( bool) {
	.markAccessPoint()
	if .isVar && !.isArg {
		.scope.c.emit(&loadMixed{name: .name, callee: })
	} else {
		.scope.c.emit(&loadMixedLex{name: .name, callee: })
	}
}

func ( *binding) ( bool) {
	.markAccessPoint()
	if .isVar && !.isArg {
		.scope.c.emit(&resolveMixed{name: .name, strict: , typ: varTypeVar})
	} else {
		var  varType
		if .isConst {
			if .isStrict {
				 = varTypeStrictConst
			} else {
				 = varTypeConst
			}
		} else {
			 = varTypeLet
		}
		.scope.c.emit(&resolveMixed{name: .name, strict: , typ: })
	}
}

func ( *binding) () {
	if .isArg && !.scope.argsInStash {
		.scope.moveArgsToStash()
	} else {
		.inStash = true
		.scope.needStash = true
	}
}

func ( *binding) () ( int) {
	for ,  := range .accessPoints {
		 += len(*)
	}
	return
}

type scope struct {
	c          *compiler
	prg        *Program
	outer      *scope
	nested     []*scope
	boundNames map[unistring.String]*binding
	bindings   []*binding
	base       int
	numArgs    int

	// function type. If not funcNone, this is a function or a top-level lexical environment
	funcType funcType

	// in strict mode
	strict bool
	// eval top-level scope
	eval bool
	// at least one inner scope has direct eval() which can lookup names dynamically (by name)
	dynLookup bool
	// at least one binding has been marked for placement in stash
	needStash bool

	// is a variable environment, i.e. the target for dynamically created var bindings
	variable bool
	// a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically
	dynamic bool
	// arguments have been marked for placement in stash (functions only)
	argsInStash bool
	// need 'arguments' object (functions only)
	argsNeeded bool
}

type block struct {
	typ        blockType
	label      unistring.String
	cont       int
	breaks     []int
	conts      []int
	outer      *block
	breaking   *block // set when the 'finally' block is an empty break statement sequence
	needResult bool
}

func ( *compiler) ( *enterBlock) {
	.updateEnterBlock()
	 := &leaveBlock{
		stackSize: .stackSize,
		popStash:  .stashSize > 0,
	}
	.emit()
	for ,  := range .block.breaks {
		.p.code[] = 
	}
	.block.breaks = nil
	.leaveBlock()
}

func ( *compiler) () {
	 := len(.p.code)
	for ,  := range .block.breaks {
		.p.code[] = jump( - )
	}
	if  := .block.typ;  == blockLoop ||  == blockLoopEnum {
		for ,  := range .block.conts {
			.p.code[] = jump(.block.cont - )
		}
	}
	.block = .block.outer
}

func ( *CompilerSyntaxError) () string {
	if .File != nil {
		return fmt.Sprintf("SyntaxError: %s at %s", .Message, .File.Position(.Offset))
	}
	return fmt.Sprintf("SyntaxError: %s", .Message)
}

func ( *CompilerReferenceError) () string {
	return fmt.Sprintf("ReferenceError: %s", .Message)
}

func ( *compiler) () {
	 := false
	if .scope != nil {
		 = .scope.strict
	}
	.scope = &scope{
		c:      ,
		prg:    .p,
		outer:  .scope,
		strict: ,
	}
}

func ( *compiler) () {
	.newScope()
	if  := .scope.outer;  != nil {
		.nested = append(.nested, .scope)
	}
	.scope.base = len(.p.code)
}

func ( *compiler) () {
	.scope = .scope.outer
}

func ( *compiler) ( String) {
	 := .string()
	if .stringCache == nil {
		.stringCache = make(map[unistring.String]Value)
	}
	 := .stringCache[]
	if  == nil {
		.stringCache[] = 
		 = 
	}

	.emit(loadVal{})
}

func ( *compiler) ( Value) {
	if ,  := .(String);  {
		.emitLiteralString()
		return
	}

	.emit(loadVal{})
}

func newCompiler() *compiler {
	 := &compiler{
		p: &Program{},
	}

	.enumGetExpr.init(, file.Idx(0))

	return 
}

func ( *Program) ( func( string,  ...interface{})) {
	._dumpCode("", )
}

func ( *Program) ( string,  func( string,  ...interface{})) {
	 := func( *Program) {
		 :=  + ">"
		("%s ---- init_fields:", )
		.(, )
		("%s ----", )
	}
	for ,  := range .code {
		("%s %d: %T(%v)", , , , )
		var  *Program
		switch f := .(type) {
		case newFuncInstruction:
			 = .getPrg()
		case *newDerivedClass:
			if .initFields != nil {
				(.initFields)
			}
			 = .ctor
		case *newClass:
			if .initFields != nil {
				(.initFields)
			}
			 = .ctor
		case *newStaticFieldInit:
			if .initFields != nil {
				(.initFields)
			}
		}
		if  != nil {
			.(+">", )
		}
	}
}

func ( *Program) ( int) int {
	 := sort.Search(len(.srcMap), func( int) bool {
		return .srcMap[].pc > 
	}) - 1
	if  >= 0 {
		return .srcMap[].srcPos
	}

	return 0
}

func ( *Program) ( int) {
	if len(.srcMap) > 0 && .srcMap[len(.srcMap)-1].srcPos ==  {
		return
	}
	.srcMap = append(.srcMap, srcMapItem{pc: len(.code), srcPos: })
}

func ( *scope) ( unistring.String) ( *binding,  bool) {
	 = true
	 := false
	for  := ; ;  = .outer {
		if .outer != nil {
			if ,  := .boundNames[];  {
				if  && !.inStash {
					.moveToStash()
				}
				 = 
				return
			}
		} else {
			 = false
			return
		}
		if .dynamic {
			 = false
		}
		if  == "arguments" && .funcType != funcNone && .funcType != funcArrow {
			if .funcType == funcClsInit {
				.c.throwSyntaxError(0, "'arguments' is not allowed in class field initializer or static initialization block")
			}
			.argsNeeded = true
			, _ = .bindName()
			return
		}
		if .isFunction() {
			 = true
		}
	}
}

func ( *scope) () (*binding, bool) {
	 := false
	for  := ;  != nil;  = .outer {
		if .outer == nil {
			if .eval {
				return nil, true
			}
		}
		if ,  := .boundNames[thisBindingName];  {
			if  && !.inStash {
				.moveToStash()
			}
			return , false
		}
		if .isFunction() {
			 = true
		}
	}
	return nil, false
}

func ( *scope) () {
	if .boundNames == nil {
		.boundNames = make(map[unistring.String]*binding)
	}
}

func ( *scope) ( int) *binding {
	if len(.bindings) >= (1<<24)-1 {
		.c.throwSyntaxError(, "Too many variables")
	}
	 := &binding{
		scope: ,
	}
	.bindings = append(.bindings, )
	return 
}

func ( *scope) ( unistring.String,  bool,  int) (*binding, bool) {
	if  := .boundNames[];  != nil {
		if  {
			.c.throwSyntaxError(, "Identifier '%s' has already been declared", )
		}
		return , false
	}
	 := .addBinding()
	.name = 
	.ensureBoundNamesCreated()
	.boundNames[] = 
	return , true
}

func ( *scope) () *binding {
	,  := .bindNameLexical(thisBindingName, false, 0)
	.isVar = true // don't check on load
	return 
}

func ( *scope) ( unistring.String) (*binding, bool) {
	if !.isFunction() && !.variable && .outer != nil {
		return .outer.()
	}
	,  := .bindNameLexical(, false, 0)
	if  {
		.isVar = true
	}
	return , 
}

func ( *scope) ( unistring.String) (*binding, bool) {
	if !.isFunction() && .outer != nil {
		return .outer.()
	}

	,  := .boundNames[]
	 := &binding{
		scope: ,
		name:  ,
	}
	.bindings = append(.bindings, )
	.ensureBoundNamesCreated()
	.boundNames[] = 
	return , !
}

func ( *scope) () *scope {
	for  := ;  != nil;  = .outer {
		if .isFunction() {
			return 
		}
	}
	return nil
}

func ( *scope) () *scope {
	for  := ;  != nil;  = .outer {
		if .eval || .isFunction() && .funcType != funcArrow {
			return 
		}
	}
	return nil
}

func ( *scope) ( int) (,  int) {
	 := false
	if  := .nearestFunction();  != nil {
		 = .argsInStash
	}
	,  := 0, 0
	 := .isDynamic()
	var  bool
	if  := .nearestThis();  != nil && .funcType == funcDerivedCtor {
		 = true
	}
	for ,  := range .bindings {
		var  bool
		if .name == thisBindingName {
			 = true
		}
		if  || .inStash {
			for ,  := range .accessPoints {
				var  uint32
				for  := ;  != nil &&  != ;  = .outer {
					if .needStash || .isDynamic() {
						++
					}
				}
				if  > 255 {
					.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded")
				}
				 := ( << 24) | uint32()
				 := .base
				 := .prg.code
				if  {
					if  {
						for ,  := range * {
							 := &[+]
							switch (*).(type) {
							case loadStack:
								* = loadThisStash()
							case initStack:
								* = initStash()
							case resolveThisStack:
								* = resolveThisStash()
							case _ret:
								* = cret()
							default:
								.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for 'this'")
							}
						}
					} else {
						for ,  := range * {
							 := &[+]
							switch (*).(type) {
							case loadStack:
								* = loadStash()
							case initStack:
								* = initStash()
							default:
								.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for 'this'")
							}
						}
					}
				} else {
					for ,  := range * {
						 := &[+]
						switch i := (*).(type) {
						case loadStack:
							* = loadStash()
						case storeStack:
							* = storeStash()
						case storeStackP:
							* = storeStashP()
						case loadStackLex:
							* = loadStashLex()
						case storeStackLex:
							* = storeStashLex()
						case storeStackLexP:
							* = storeStashLexP()
						case initStackP:
							* = initStashP()
						case initStack:
							* = initStash()
						case *loadMixed:
							.idx = 
						case *loadMixedLex:
							.idx = 
						case *resolveMixed:
							.idx = 
						default:
							.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for binding: %T", )
						}
					}
				}
			}
			++
		} else {
			var  int
			if ! {
				if  < .numArgs {
					 = -( + 1)
				} else {
					++
					 =  + 
				}
			}
			for ,  := range .accessPoints {
				var  int
				for  := ;  != nil &&  != ;  = .outer {
					if .needStash || .isDynamic() {
						++
					}
				}
				if  > 255 {
					.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded")
				}
				 := .prg.code
				 := .base
				if  {
					if  {
						for ,  := range * {
							 := &[+]
							switch (*).(type) {
							case loadStack:
								* = loadThisStack{}
							case initStack:
								// no-op
							case resolveThisStack:
								// no-op
							case _ret:
								// no-op, already in the right place
							default:
								.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for 'this'")
							}
						}
					} /*else {
						no-op
					}*/
				} else if  {
					for ,  := range * {
						 := &[+]
						switch i := (*).(type) {
						case loadStack:
							* = loadStack1()
						case storeStack:
							* = storeStack1()
						case storeStackP:
							* = storeStack1P()
						case loadStackLex:
							* = loadStack1Lex()
						case storeStackLex:
							* = storeStack1Lex()
						case storeStackLexP:
							* = storeStack1LexP()
						case initStackP:
							* = initStack1P()
						case initStack:
							* = initStack1()
						case *loadMixed:
							* = &loadMixedStack1{name: .name, idx: , level: uint8(), callee: .callee}
						case *loadMixedLex:
							* = &loadMixedStack1Lex{name: .name, idx: , level: uint8(), callee: .callee}
						case *resolveMixed:
							* = &resolveMixedStack1{typ: .typ, name: .name, idx: , level: uint8(), strict: .strict}
						default:
							.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for binding: %T", )
						}
					}
				} else {
					for ,  := range * {
						 := &[+]
						switch i := (*).(type) {
						case loadStack:
							* = loadStack()
						case storeStack:
							* = storeStack()
						case storeStackP:
							* = storeStackP()
						case loadStackLex:
							* = loadStackLex()
						case storeStackLex:
							* = storeStackLex()
						case storeStackLexP:
							* = storeStackLexP()
						case initStack:
							* = initStack()
						case initStackP:
							* = initStackP()
						case *loadMixed:
							* = &loadMixedStack{name: .name, idx: , level: uint8(), callee: .callee}
						case *loadMixedLex:
							* = &loadMixedStackLex{name: .name, idx: , level: uint8(), callee: .callee}
						case *resolveMixed:
							* = &resolveMixedStack{typ: .typ, name: .name, idx: , level: uint8(), strict: .strict}
						default:
							.c.assert(false, .c.p.sourceOffset(), "Unsupported instruction for binding: %T", )
						}
					}
				}
			}
		}
	}
	for ,  := range .nested {
		.( + )
	}
	return , 
}

func ( *scope) () {
	for ,  := range .bindings {
		if !.isArg {
			break
		}
		.inStash = true
	}
	.argsInStash = true
	.needStash = true
}

func ( *compiler) ( int) {
	 := .p.code[:]
	 := make([]instruction, len())
	copy(, )
	if cap(.codeScratchpad) < cap(.p.code) {
		.codeScratchpad = .p.code[:0]
	}
	.p.code = 
}

func ( *scope) ( int) {
	.c.trimCode()
	if  != 0 {
		 := .c.p.srcMap
		for  := range  {
			[].pc -= 
		}
		.adjustBase(-)
	}
}

func ( *scope) ( int) {
	.base += 
	for ,  := range .nested {
		.()
	}
}

func ( *scope) () map[unistring.String]uint32 {
	 := len(.bindings)
	if  == 0 {
		return nil
	}
	 := make(map[unistring.String]uint32, )
	for ,  := range .bindings {
		 := uint32()
		if .isConst {
			 |= maskConst
			if .isStrict {
				 |= maskStrict
			}
		}
		if .isVar {
			 |= maskVar
		}
		[.name] = 
	}
	return 
}

func ( *scope) () bool {
	return .dynLookup || .dynamic
}

func ( *scope) () bool {
	return .funcType != funcNone && !.eval
}

func ( *scope) ( *binding) {
	 := 0
	for ,  := range .bindings {
		if  ==  {
			 = 
			goto 
		}
	}
	return
:
	delete(.boundNames, .name)
	copy(.bindings[:], .bindings[+1:])
	 := len(.bindings) - 1
	.bindings[] = nil
	.bindings = .bindings[:]
}

func ( *compiler) ( *ast.Program, ,  bool,  *vm) {
	.ctxVM = 

	 :=  != nil
	.p.src = .File
	.newScope()
	 := .scope
	.dynamic = true
	.eval = 
	if ! && len(.Body) > 0 {
		 = .isStrict(.Body) != nil
	}
	.strict = 
	 :=  && 
	 := ! || 
	if  {
		.newBlockScope()
		 = .scope
		.variable = true
	}
	if  && ! {
		for  := .stash;  != nil;  = .outer {
			if  := .funcType;  != funcNone &&  != funcArrow {
				.funcType = 
				break
			}
		}
	}
	 := .extractFunctions(.Body)
	.createFunctionBindings()
	 := len(.bindings)
	if  && ! {
		if  == len() {
			.compileFunctionsGlobalAllUnique()
		} else {
			.compileFunctionsGlobal()
		}
	}
	.compileDeclList(.DeclarationList, false)
	 := len(.bindings) - 
	 := make([]unistring.String, len(.bindings))
	for ,  := range .bindings {
		[] = .name
	}
	if len() > 0 && ! &&  {
		if  {
			.emit(&bindGlobal{
				vars:      [:],
				funcs:     [:],
				deletable: ,
			})
		} else {
			.emit(&bindVars{names: , deletable: })
		}
	}
	var  *enterBlock
	if .compileLexicalDeclarations(.Body,  || !) {
		if  {
			.block = &block{
				outer:      .block,
				typ:        blockScope,
				needResult: true,
			}
			 = &enterBlock{}
			.emit()
		}
	}
	if len(.bindings) > 0 && ! {
		var ,  []unistring.String
		for ,  := range .scope.bindings[+:] {
			if .isConst {
				 = append(, .name)
			} else {
				 = append(, .name)
			}
		}
		.emit(&bindGlobal{
			vars:   [:],
			funcs:  [:],
			lets:   ,
			consts: ,
		})
	}
	if ! ||  {
		.compileFunctions()
	}
	.compileStatements(.Body, true)
	if  != nil {
		.leaveScopeBlock()
		.popScope()
	}

	.finaliseVarAlloc(0)
	.stringCache = nil
}

func ( *compiler) ( []*ast.VariableDeclaration,  bool) {
	for ,  := range  {
		.createVarBindings(, )
	}
}

func ( *compiler) ( ast.Statement) ast.Statement {
	if ,  := .(*ast.LabelledStatement);  {
		return .(.Statement)
	}
	return 
}

func ( *compiler) ( []ast.Statement) ( []*ast.FunctionDeclaration) {
	for ,  := range  {
		var  *ast.FunctionDeclaration
		switch st := .extractLabelled().(type) {
		case *ast.FunctionDeclaration:
			 = 
		case *ast.LabelledStatement:
			if ,  := .Statement.(*ast.FunctionDeclaration);  {
				 = 
			} else {
				continue
			}
		default:
			continue
		}
		 = append(, )
	}
	return
}

func ( *compiler) ( []*ast.FunctionDeclaration) {
	 := .scope
	if .outer != nil {
		 := !.isFunction() && !.variable && .strict
		if ! {
			 := false
			for ,  := range  {
				if !.Function.Async && !.Function.Generator {
					.bindNameLexical(.Function.Name.Name, false, int(.Function.Name.Idx1())-1)
				} else {
					 = true
				}
			}
			if  {
				for ,  := range  {
					if .Function.Async || .Function.Generator {
						.bindNameLexical(.Function.Name.Name, true, int(.Function.Name.Idx1())-1)
					}
				}
			}
		} else {
			for ,  := range  {
				.bindNameLexical(.Function.Name.Name, true, int(.Function.Name.Idx1())-1)
			}
		}
	} else {
		for ,  := range  {
			.bindName(.Function.Name.Name)
		}
	}
}

func ( *compiler) ( []*ast.FunctionDeclaration) {
	for ,  := range  {
		.compileFunction()
	}
}

func ( *compiler) ( []*ast.FunctionDeclaration) {
	for ,  := range  {
		.compileFunctionLiteral(.Function, false).emitGetter(true)
	}
}

func ( *compiler) ( []*ast.FunctionDeclaration) {
	 := make(map[unistring.String]int, len())
	for  := len() - 1;  >= 0; -- {
		 := [].Function.Name.Name
		if ,  := []; ! {
			[] = 
		}
	}
	 := 0
	for ,  := range  {
		 := .Function.Name.Name
		if [] ==  {
			.compileFunctionLiteral(.Function, false).emitGetter(true)
			.scope.bindings[] = .scope.boundNames[]
			++
		} else {
			 := .enterDummyMode()
			.compileFunctionLiteral(.Function, false).emitGetter(false)
			()
		}
	}
}

func ( *compiler) ( unistring.String,  int,  bool) {
	if .scope.strict {
		.checkIdentifierLName(, )
		.checkIdentifierName(, )
	}
	if ! ||  != "arguments" {
		.scope.bindName()
	}
}

func ( *compiler) ( ast.Expression,  func( unistring.String,  int)) {
	switch target := .(type) {
	case *ast.Identifier:
		(.Name, int(.Idx)-1)
	case *ast.ObjectPattern:
		for ,  := range .Properties {
			switch prop := .(type) {
			case *ast.PropertyShort:
				(.Name.Name, int(.Name.Idx)-1)
			case *ast.PropertyKeyed:
				.(.Value, )
			default:
				.throwSyntaxError(int(.Idx0()-1), "unsupported property type in ObjectPattern: %T", )
			}
		}
		if .Rest != nil {
			.(.Rest, )
		}
	case *ast.ArrayPattern:
		for ,  := range .Elements {
			if  != nil {
				.(, )
			}
		}
		if .Rest != nil {
			.(.Rest, )
		}
	case *ast.AssignExpression:
		.(.Left, )
	default:
		.throwSyntaxError(int(.Idx0()-1), "unsupported binding target: %T", )
	}
}

func ( *compiler) ( ast.Expression,  bool) {
	.createBindings(, func( unistring.String,  int) {
		.createVarIdBinding(, , )
	})
}

func ( *compiler) ( *ast.VariableDeclaration,  bool) {
	for ,  := range .List {
		.createVarBinding(.Target, )
	}
}

func ( *compiler) ( unistring.String,  bool,  int) *binding {
	if  == "let" {
		.throwSyntaxError(, "let is disallowed as a lexically bound name")
	}
	if .scope.strict {
		.checkIdentifierLName(, )
		.checkIdentifierName(, )
	}
	,  := .scope.bindNameLexical(, true, )
	if  {
		.isConst, .isStrict = true, true
	}
	return 
}

func ( *compiler) ( unistring.String,  bool,  int,  *binding) *binding {
	if  == "let" {
		.throwSyntaxError(, "let is disallowed as a lexically bound name")
	}
	if .scope.strict {
		.checkIdentifierLName(, )
		.checkIdentifierName(, )
	}
	 := .scope.outer
	 := .boundNames[]
	if  != nil {
		if  !=  && ( != "arguments" || !.argsNeeded) {
			.throwSyntaxError(, "Identifier '%s' has already been declared", )
		}
	}
	,  := .scope.bindNameLexical(, true, )
	if  {
		.isConst, .isStrict = true, true
	}
	return 
}

func ( *compiler) ( ast.Expression,  bool) {
	.createBindings(, func( unistring.String,  int) {
		.createLexicalIdBinding(, , )
	})
}

func ( *compiler) ( *ast.LexicalDeclaration) {
	for ,  := range .List {
		.createLexicalBinding(.Target, .Token == token.CONST)
	}
}

func ( *compiler) ( []ast.Statement,  bool) bool {
	for ,  := range  {
		if ,  := .(*ast.LexicalDeclaration);  {
			if ! {
				.newBlockScope()
				 = true
			}
			.createLexicalBindings()
		} else if ,  := .(*ast.ClassDeclaration);  {
			if ! {
				.newBlockScope()
				 = true
			}
			.createLexicalIdBinding(.Class.Name.Name, false, int(.Class.Name.Idx)-1)
		}
	}
	return 
}

func ( *compiler) ( []ast.Statement,  *binding) {
	for ,  := range  {
		if ,  := .(*ast.LexicalDeclaration);  {
			 := .Token == token.CONST
			for ,  := range .List {
				.createBindings(.Target, func( unistring.String,  int) {
					.createLexicalIdBindingFuncBody(, , , )
				})
			}
		} else if ,  := .(*ast.ClassDeclaration);  {
			.createLexicalIdBindingFuncBody(.Class.Name.Name, false, int(.Class.Name.Idx)-1, )
		}
	}
}

func ( *compiler) ( *ast.FunctionDeclaration) {
	 := .Function.Name.Name
	 := .scope.boundNames[]
	if  == nil || .isVar {
		 := &compiledIdentifierExpr{
			name: .Function.Name.Name,
		}
		.init(, .Function.Idx0())
		.emitSetter(.compileFunctionLiteral(.Function, false), false)
	} else {
		.compileFunctionLiteral(.Function, false).emitGetter(true)
		.emitInitP()
	}
}

func ( *compiler) ( *ast.FunctionDeclaration) {
	if .Function.Async {
		.throwSyntaxError(int(.Idx0())-1, "Async functions can only be declared at top level or inside a block.")
	}
	if .Function.Generator {
		.throwSyntaxError(int(.Idx0())-1, "Generators can only be declared at top level or inside a block.")
	}
	if .scope.strict {
		.throwSyntaxError(int(.Idx0())-1, "In strict mode code, functions can only be declared at top level or inside a block.")
	}
	.throwSyntaxError(int(.Idx0())-1, "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.")
}

func ( *compiler) ( ...instruction) {
	.p.code = append(.p.code, ...)
}

func ( *compiler) ( int,  string,  ...interface{}) {
	panic(&CompilerSyntaxError{
		CompilerError: CompilerError{
			File:    .p.src,
			Offset:  ,
			Message: fmt.Sprintf(, ...),
		},
	})
}

func ( *compiler) ( []ast.Statement) *ast.StringLiteral {
	for ,  := range  {
		if ,  := .(*ast.ExpressionStatement);  {
			if ,  := .Expression.(*ast.StringLiteral);  {
				if .Literal == `"use strict"` || .Literal == `'use strict'` {
					return 
				}
			} else {
				break
			}
		} else {
			break
		}
	}
	return nil
}

func ( *compiler) ( ast.Statement) *ast.StringLiteral {
	if ,  := .(*ast.BlockStatement);  {
		return .isStrict(.List)
	}
	return nil
}

func ( *compiler) ( unistring.String,  int) {
	switch  {
	case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield":
		.throwSyntaxError(, "Unexpected strict mode reserved word")
	}
}

func ( *compiler) ( unistring.String,  int) {
	switch  {
	case "eval", "arguments":
		.throwSyntaxError(, "Assignment to eval or arguments is not allowed in strict mode")
	}
}

// Enter a 'dummy' compilation mode. Any code produced after this method is called will be discarded after
// leaveFunc is called with no additional side effects. This is useful for compiling code inside a
// constant falsy condition 'if' branch or a loop (i.e 'if (false) { ... } or while (false) { ... }).
// Such code should not be included in the final compilation result as it's never called, but it must
// still produce compilation errors if there are any.
// TODO: make sure variable lookups do not de-optimise parent scopes
func ( *compiler) () ( func()) {
	,  := .block, .p
	if  != nil {
		.block = &block{
			typ:      .typ,
			label:    .label,
			outer:    .outer,
			breaking: .breaking,
		}
	}
	.p = &Program{
		src: .p.src,
	}
	.newScope()
	return func() {
		.block, .p = , 
		.popScope()
	}
}

func ( *compiler) ( ast.Statement) {
	 := .enterDummyMode()
	.compileStatement(, false)
	()
}

func ( *compiler) ( bool,  int,  string,  ...interface{}) {
	if ! {
		.throwSyntaxError(, "Compiler bug: "+, ...)
	}
}

func privateIdString( unistring.String) unistring.String {
	return asciiString("#").Concat(stringValueFromRaw()).string()
}

type privateName struct {
	idx                  int
	isStatic             bool
	isMethod             bool
	hasGetter, hasSetter bool
}

type resolvedPrivateName struct {
	name     unistring.String
	idx      uint32
	level    uint8
	isStatic bool
	isMethod bool
}

func ( *resolvedPrivateName) () unistring.String {
	return privateIdString(.name)
}

type privateEnvRegistry struct {
	fields, methods []unistring.String
}

type classScope struct {
	c            *compiler
	privateNames map[unistring.String]*privateName

	instanceEnv, staticEnv privateEnvRegistry

	outer *classScope
}

func ( *privateEnvRegistry) ( unistring.String) int {
	.methods = append(.methods, )
	return len(.methods) - 1
}

func ( *privateEnvRegistry) ( unistring.String) int {
	.fields = append(.fields, )
	return len(.fields) - 1
}

func ( *classScope) ( unistring.String,  ast.PropertyKind,  bool,  int) {
	 := .privateNames[]
	if  != nil {
		if .isStatic ==  {
			switch  {
			case ast.PropertyKindGet:
				if .hasSetter && !.hasGetter {
					.hasGetter = true
					return
				}
			case ast.PropertyKindSet:
				if .hasGetter && !.hasSetter {
					.hasSetter = true
					return
				}
			}
		}
		.c.throwSyntaxError(, "Identifier '#%s' has already been declared", )
		panic("unreachable")
	}
	var  *privateEnvRegistry
	if  {
		 = &.staticEnv
	} else {
		 = &.instanceEnv
	}

	 = &privateName{
		isStatic:  ,
		hasGetter:  == ast.PropertyKindGet,
		hasSetter:  == ast.PropertyKindSet,
	}
	if  != ast.PropertyKindValue {
		.idx = .createPrivateMethodId()
		.isMethod = true
	} else {
		.idx = .createPrivateFieldId()
	}

	if .privateNames == nil {
		.privateNames = make(map[unistring.String]*privateName)
	}
	.privateNames[] = 
}

func ( *classScope) ( unistring.String) *privateName {
	if  := .privateNames[];  != nil {
		return 
	}
	.c.assert(false, 0, "getDeclaredPrivateId() for undeclared id")
	panic("unreachable")
}

func ( *compiler) ( unistring.String,  int) (*resolvedPrivateName, *privateId) {
	 := 0
	for  := .classScope;  != nil;  = .outer {
		if len(.privateNames) > 0 {
			if  := .privateNames[];  != nil {
				return &resolvedPrivateName{
					name:     ,
					idx:      uint32(.idx),
					level:    uint8(),
					isStatic: .isStatic,
					isMethod: .isMethod,
				}, nil
			}
			++
		}
	}
	if .ctxVM != nil {
		for  := .ctxVM.privEnv;  != nil;  = .outer {
			if  := .names[];  != nil {
				return nil, 
			}
		}
	}
	.throwSyntaxError(, "Private field '#%s' must be declared in an enclosing class", )
	panic("unreachable")
}