# imba$inlineHelpers=1
# imba$v2=0
# TODO Create Expression - make all expressions inherit from these?

var helpers = require './helpers'
var constants = require './constants'
var fspath = require 'path'
import {conv} from '../../vendor/colors'
import {colord} from './colord'

import ImbaParseError,ImbaTraverseError from './errors'
import Token from './token'
import SourceMap from './sourcemap'

import StyleRule,StyleTheme,Color,StyleSheet,parseColorString from './styler.imba'
import ReservedIdentifierRegex,InternalPrefixes,toJSIdentifier,toCustomTagIdentifier from '../utils/identifiers.imba'
import Compilation from './compilation'

import SourceMapper from './sourcemapper'

import {ClassFlags} from '../imba/runtime.mjs'

import extractGenericNames from './utils'

class MappedString
	def initialize value, source
		@value = value
		@source = source

	def startLoc
		@source.startLoc

	def endLoc
		@source.endLoc

	def toString
		@value

	def c
		M(@value,self)

class Templated
	def initialize template, options, source
		@template = template
		@options = options
		@source = source

	def c
		TPL(@options,@template)

	def toString
		c

class InternalName

	def initialize value, source
		@source = source or value
		if value:toClassName
			value = value.toClassName

		if value:c isa Function
			value = value.c(mark: no)


		value = "Ω" + SourceMapper.strip(value).split(".").join("__")
		let nr = STACK.incr(value)
		if nr > 1
			value += "Ω" + nr
		@value = value

	def startLoc
		@source.startLoc

	def endLoc
		@source.endLoc

	def toString
		@value

	def c
		@value

var TAG_NAMES = constants.TAG_NAMES
var TAG_GLOBAL_ATTRIBUTES = constants.TAG_GLOBAL_ATTRIBUTES

var TAG_TYPES = {}
var TAG_ATTRS = {}
var TSC = no

var USE_SAFE_RENDER_SELF = yes

var CONTEXT = {}

var GLOBAL_INTERFACES = {
	Array: {interface: 'ArrayConstructor'}
	Number: {interface: 'NumberConstructor',thistype: 'number'}
	String: {interface: 'StringConstructor',thistype: 'string'}
	Object: {interface: 'ObjectConstructor'}
	Math: {namespace: 'Math'}
	window: {global: yes}

}

var EXT_LOADER_MAP = {
	svg: 'image'
	png: 'image'
	apng: 'image'
	jpg: 'image'
	jpeg: 'image'
	gif: 'image'
	tiff: 'image'
	bmp: 'image'
}

TAG_TYPES.HTML = "a abbr address area article aside audio b base bdi bdo big blockquote body br
 button canvas caption cite code col colgroup data datalist dd del details dfn
 div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6
 head header hr html i iframe img input ins kbd keygen label legend li link
 main map mark menu menuitem meta meter nav noscript object ol optgroup option
 output p param pre progress q rp rt ruby s samp script section select small
 source span strong strike style sub summary sup table tbody td textarea tfoot th
 thead time title tr track u ul var video wbr".split(" ")

TAG_TYPES.SVG = "circle defs ellipse g line linearGradient mask path pattern polygon polyline radialGradient rect stop svg text tspan".split(" ")

TAG_ATTRS.HTML = "accept accessKey action allowFullScreen allowTransparency alt async
 autoComplete autoFocus autoPlay cellPadding cellSpacing charSet checked
 className cols colSpan content contentEditable contextMenu controls coords
 crossOrigin data dateTime defer dir disabled download draggable encType form
 formNoValidate frameBorder height hidden href hrefLang htmlFor httpEquiv icon
 id label lang list loop max maxLength mediaGroup method min multiple muted
 name noValidate pattern placeholder poster preload radioGroup readOnly rel
 required role rows rowSpan sandbox scope scrollLeft scrolling scrollTop
 seamless selected shape size span spellCheck src srcDoc srcSet start step
 style tabIndex target title type useMap value width wmode"

TAG_ATTRS.SVG = "cx cy d dx dy fill fillOpacity fontFamily fontSize fx fy gradientTransform
 gradientUnits markerEnd markerMid markerStart offset opacity
 patternContentUnits patternUnits points preserveAspectRatio r rx ry
 spreadMethod stopColor stopOpacity stroke strokeDasharray strokeLinecap
 strokeOpacity strokeWidth textAnchor transform version viewBox x1 x2 x y1 y2 y"

var CUSTOM_EVENTS = {
	intersect: 'events_intersect'
	selection: 'events_selection'
	resize: 'events_resize'
	mutate: 'events_mutate'
	hotkey: 'events_hotkey'
	touch: 'events_touch'

	pointer: 'events_pointer'
	pointerdown: 'events_pointer'
	pointermove: 'events_pointer'
	pointerover: 'events_pointer'
	pointerout: 'events_pointer'
	pointerup: 'events_pointer'
	pointercancel: 'events_pointer'
	lostpointercapture: 'events_pointer'

	click: 'events_mouse'
	mousedown: 'events_mouse'
	mouseup: 'events_mouse'
	mouseenter: 'events_mouse'
	mouseleave: 'events_mouse'
	mousemove: 'events_mouse'
	mouseout: 'events_mouse'
	mouseover: 'events_mouse'
	mousewheel: 'events_mouse'

	keydown: 'events_keyboard'
	keyup: 'events_keyboard'
	keypress: 'events_keyboard'
}

export var AST = {}

var TREE_TYPE =
	DYNAMIC: 1
	STATIC: 2
	SINGLE: 3
	OPTLOOP: 4
	LOOP: 5

export var F =
	TAG_INITED: 2 ** 0
	TAG_BUILT: 2 ** 1 # available
	TAG_CUSTOM: 2 ** 2 # available
	TAG_AWAKENED: 2 ** 3
	TAG_MOUNTED: 2 ** 4
	TAG_SCHEDULE: 2 ** 5 # available
	TAG_SCHEDULED: 2 ** 6
	TAG_FIRST_CHILD: 2 ** 7
	TAG_LAST_CHILD: 2 ** 8
	TAG_HAS_DYNAMIC_FLAGS: 2 ** 9
	TAG_HAS_BRANCHES: 2 ** 10
	TAG_HAS_LOOPS: 2 ** 11
	TAG_HAS_DYNAMIC_CHILDREN: 2 ** 12
	TAG_IN_BRANCH: 2 ** 13
	TAG_BIND_MODEL: 2 ** 14
	TAG_INDEXED: 2 ** 15 # not used
	TAG_KEYED: 2 ** 16 # not used

	EL_INITED: 2 ** 0
	EL_HYDRATED: 2 ** 1
	EL_HYDRATING: 2 ** 2
	EL_AWAKENED: 2 ** 3
	EL_MOUNTING: 2 ** 4
	EL_MOUNTED: 2 ** 5
	EL_SCHEDULE: 2 ** 6 # available
	EL_SCHEDULED: 2 ** 7
	EL_RENDERING: 2 ** 8
	EL_RENDERED: 2 ** 9
	EL_SSR: 2 ** 10
	EL_TRACKED: 2 ** 11 # emit mount/unmount events
	EL_SUSPENDED: 2 ** 12 # block commit from rendering
	EL_UNRENDERED: 2 ** 13
	EL_MOVING: 2 ** 14

	# render marks
	DIFF_BUILT: 2 ** 0
	DIFF_FLAGS: 2 ** 1
	DIFF_ATTRS: 2 ** 2
	DIFF_CHILDREN: 2 ** 3
	DIFF_MODIFIERS: 2 ** 4
	DIFF_INLINE: 2 ** 5

var NESTED_TPL_REGEX = ///
	@\{(@(\!?\w+)\? )?
	([^{}]*(:?\{
		([^{}]*(:?\{[^{}]*\}[^{}]*)*)	
	\}[^{}]*)*)
	\}
///g

export var TPL = do |vars,string,o = {}, d = 0|
	STACK.call(template: string) do
		string = string.replace(/\%/g,'@')

		# lazy to use regex but it works for these simple templates
		string = string.replace(NESTED_TPL_REGEX) do|m,g,cond,subtpl|
			if cond
				if cond[0] == '!'
					return 'εε' if vars[cond.slice(1)]
				else
					return 'εε' unless vars[cond]

			let o = {}
			let sub = TPL(vars,subtpl,o,d + 1)
			(o:replaced or cond) ? sub : 'εε'

		string = string.replace(/\@([\w\-]+)\|?/g) do|m,k|
			if k == 'ts-ignore'
				return '@ts-ignore'

			let v = vars[k]

			if v === yes
				o:replaced = yes
				return k
			
			v = M(v) if v
			o:replaced = yes if v

			return v or 'εε'

		string = string.replace(/(\n\s*)(εε)\s*(?=\n)/g,'') # .replace(/εε/g,'')
		string = string.replace(/(^|\s)(εε[ ]?)*/mg,'$1').replace(/εε/g,'')
		string = string.replace(/(^[ ]+)/mg,'')
		string = string.trim() if d == 0
		string

export var DECLARE = do |node,keyword|
	if !Compilation:current:tsc
		return LIT('')

	node.set(declareOnly: keyword)

export var GLOBAL = do |node,keyword|	
	return node.set('global': keyword)
	
export var SETTYPE = do |node,type,ctx|
	# dont even include any types when not in TSC mode
	unless !!Compilation:current:tsc
		return node
	# console.log "set datatype {node} {type:constructor:name}",!!Compilation:current:tsc
	if type isa Generics
		node.set(generics: type)
		return node
		# return node.set(generics: type)

	return node.set(datatype: type)


# Helpers for operators
export var OP = do |op, l, r|
	var o = String(op)

	switch o
		when '.','?.'
			if l isa Super and !l.member

				l.member = r
				return l

			r = Identifier.new(r) if r isa String
			# r = r.value if r isa VarOrAccess

			# if r.@value and r.@value.@value == 'new'
			#	# TODO remove support for this
			#	return New.new(l).set(keyword: r)

			Access.new(op,l,r)
		when '='
			Assign.new(op,l,r)

		when '~='
			return OP('&=',l,OP('~',r))

		when '||=','&&=','??='
			ConditionalAssign.new(op,l,r)
		when '+=','-=','*=','/=','^=','%=','**='
			CompoundAssign.new(op,l,r)

		when 'instanceof','isa'
			InstanceOf.new(op,l,r)
		when 'in'
			In.new(op,l,r)
		when 'typeof'
			TypeOf.new(op,l,r)
		when 'delete'
			Delete.new(op,l,r)
		when '--','++','!','√','not','!!'
			UnaryOp.new(op,l,r)
		when '>','<','>=','<=','==','===','!=','!=='
			ComparisonOp.new(op,l,r)
		when '..','...'
			Range.new(op,l,r)
		else
			Op.new(op,l,r)

var PATHIFY = do |val|
	# console.log "PATHIFY {val}"
	if val isa TagAttrValue
		val = val.value

	if val isa ArgList
		val = val.values[0]

	while val isa Parens
		val = val.value

	if val isa VarOrAccess
		val = val.@variable or val.value

	if val isa Access
		let left = val.left
		let right = val.right isa Index ? val.right.value : val.right

		if left isa VarOrAccess
			left = left.@variable or left.value

		if right isa VarOrAccess
			right = right.@variable or right.value

		if val isa IvarAccess
			left ||= val.scope__.context

		if right isa SymbolIdentifier
			yes

		elif right isa Identifier
			right = helpers.singlequote(String(right.js))
			right = Str.new(right)

		return [left,right]

	return val

var OPTS = {}
var ROOT = null

export var NODES = []

var C = do |node,opts|
	(typeof node == 'string' or typeof node == 'number') ? node : node.c(opts)

var MLOC = do |a,b|
	b = a if b == undefined

	{
		startLoc: do a
		endLoc: do b
	}

var MPREV = [-1,-1,-1]
var M = do |val,mark,o|
	if mark == undefined
		mark = val
		o ||= {mark: false}

	if mark and mark:startLoc
		val = C(val,o)

		# what if the value itself creates a location?
		let ref = STACK.incr('sourcePair')
		let start = mark.startLoc
		let end = mark.endLoc

		let m0 = ''
		let m1 = ''

		if start === MPREV[0] and end === MPREV[1] and ref == (MPREV[2] + 1) and val.startsWith('/*%')
			if true # val.indexOf("/*%{start}|{MPREV[2]}") >= 0 and false
				STACK.decr('sourcePair')
				# only if the value contains this, no?
				return val

		if start == 0 or start > 0
			m0 = end >= start ? "/*%{start}|{ref}$*/" : "/*%{start}$*/"

		if end == 0 or end > 0
			m1 = start >= 0 ? "/*%{end}|{ref}$*/" : "/*%{end}$*/"

		MPREV = [start,end,ref]
		return m0 + val + m1

	return C(val,o)

var MSTART = do |*params|
	for item in params
		if item isa Number
			return item
		if item and item:startLoc isa Function
			return item.startLoc
	return null

var MEND = do |*params|
	for item in params
		if item isa Number
			return item
		if item and item:endLoc isa Function
			return item.endLoc
	return null

var TYP = do |item,format|
	let typ = item.@options and item.@options:datatype
	typ ||= item:datatype isa Function and item.datatype

	if typ
		let str = C(typ)
		if format == 'jsdoc'
			"/** @type \{{M(str,typ)}\} */"
		else
			":{M(str,typ)}"
	else
		""

var TYPED = do |item,typ|
	if typ
		"{C(item)}:{C(typ)}"
	else
		C(item)


var LIT = do |val,src|
	return MappedString.new(val,src) if src
	val isa RawScript ? val : RawScript.new(val)

var SYM = do |val|
	Symbol.new(val)

var KEY = do |val|
	val = val.value if val isa Token
	if val isa String
		if val.match(/^[a-zA-Z\$\_]+[\d\w\$\_]*$/)
			val = Identifier.new(val)
		else
			val = Str.new(helpers.singlequote(String(val)))
	return val

var STR = do |val|
	return val if val isa Str
	return Str.new(helpers.singlequote(String(val)))

var IF = do |cond,body,alt,o={}|
	var node = If.new(cond,body,o)
	node.addElse(alt) if alt
	node

var NODIFY = do |val|
	if val == null
		return Nil.new
	elif val == false
		return False.new
	elif val == true
		return True.new
	elif val isa String
		return STR(val)
	elif val isa Number
		return Num.new(val)
	else
		return val

var FN = do |pars,body,scope|
	let fn = Func.new(pars,body)
	if scope
		fn.@scope.@systemscope = scope
	return fn

var METH = do |pars,body|
	ClosedFunc.new(pars,body)

var CALL = do |callee,pars = []|
	# possibly return instead(!)
	Call.new(callee,pars)

var STDCALL = do |name,pars = []|
	CALL(STACK.corelib[name],pars)

var IIFE = do |body|
	# possibly return instead(!)
	IifeFunc.new([],body)

var GET = do |left,right| OP('.',left,right)

var CALLSELF = do |name,pars = []|
	var ref = Identifier.new(name)
	Call.new(OP('.',SELF,ref),pars)

var BLOCK = do
	Block.wrap([]:slice.call(arguments))

var WHILE = do |test,code|
	While.new(test).addBody(code)

export var SPLAT = do |value|
	Splat.new(value)
	# if value isa Assign
	#	value.left = Splat.new(value.left)
	#	return value
	# else
	#	Splat.new(value)

var SEMICOLON_TEST = /;(\s*\/\/.*)?[\n\s\t]*$/
var RESERVED_TEST = /^(default|char|for)$/

# captures error from parser
export def parseError str, o
	var err = Compilation.error({
		category: 'parser'
		severity: 'error'
		offset: o:offset
		length: o:length
		message: str
	})

	return err.raise()

def AST.c obj
	typeof obj == 'string' ? obj : obj.c

def AST.compileRaw item
	let o = ''
	if item isa Array
		o = '['
		for v in item
			o += AST.compileRaw(v) + ','
		o = o.slice(0,-1) + ']'

	elif item isa Object
		o = '{ '
		for own k,v of item
			# maybe quote?
			o += "{k}: {AST.compileRaw(v)},"
		o = o.slice(0,-1) + ' }'
	else
		o = JSON.stringify(item)
	return o

def AST.blk obj
	obj isa Array ? Block.wrap(obj) : obj

def AST.sym obj
	# console.log "sym {obj}"
	helpers.symbolize(String(obj),STACK)

def AST.cary ary,params = null
	ary.map do |v|
		if typeof v == 'string'
			return v
		elif v && v:c
			return params ? v.c(params) : v.c
		else
			# console.warn 'could not compile',v
			return String(v)

def AST.dump obj, key
	if obj isa Array
		obj.map do |v| v && v:dump ? v.dump(key) : v
	elif obj and obj:dump
		obj.dump

def AST.compact ary
	if ary isa ListNode
		return ary.compact

	ary.filter do |v| v != undefined && v != null

def AST.reduce res,ary
	for v in ary
		v isa Array ? AST.reduce(res,v) : res.push(v)
	return

def AST.flatten ary, compact = no
	var out = []
	for v in ary
		v isa Array ? AST.reduce(out,v) : out.push(v)
	return out

def AST.loc item
	if !item
		[0,0]
	elif item isa Token
		item.region
	elif item isa Node
		item.loc

def AST.parse str, opts = {}
	var indent = str.match(/\t+/)[0]
	# really? Require the compiler, not this
	Imbac.parse(str,opts)

def AST.inline str, opts = {}
	parse(str,opts).body

def AST.node typ, pars
	if typ == 'call'
		if pars[0].c == 'return'
			pars[0] = 'tata'
		Call.new(pars[0],pars[1],pars[2])

def AST.escapeComments str
	return '' unless str
	return str

var shortRefCache = []

def AST.counterToShortRef nr
	var base = "A".charCodeAt(0)

	nr += 30

	while shortRefCache:length <= nr
		var num = shortRefCache:length + 1
		var str = ""

		while true
			num -= 1
			str = String.fromCharCode(base + (num % 26)) + str
			num = Math.floor(num / 26)
			break unless num > 0

		shortRefCache.push(str.toLowerCase())

	return shortRefCache[nr]

def AST.truthy node

	if node isa True
		return true

	if node isa False
		return false

	if node:isTruthy
		return node.isTruthy

	return undefined

export class Indentation

	prop open
	prop close

	def initialize a,b
		@open = a
		@close = b
		self

	def isGenerated
		@open and @open:generated

	def aloc
		@open and @open.@loc or 0

	def bloc
		@close and @close.@loc or 0

	def startLoc
		aloc

	def endLoc
		bloc

	def wrap str
		var om = @open and @open.@meta
		var pre = om and om:pre or ''
		var post = om and om:post or ''
		var esc = AST:escapeComments
		var out = @close

		# the first newline should not be indented?

		str = post.replace(/^\n/,'') + str

		str = str.replace(/^/g,"\t").replace(/\n/g,"\n\t").replace(/\n\t$/g,"\n")

		str = pre + '\n' + str
		str += out.c if out isa Terminator
		str = str + '\n' unless str[str:length - 1] == '\n'

		return str

var INDENT = Indentation.new({},{})

class Stash

	def initialize
		@entities = []

	def add item
		@entities.unshift(item)
		self

	def pluck item
		var match = null
		for entity,i in @entities
			if entity == item or entity isa item
				match = entity
				@entities.splice(i,1)
				return match
		return null

export class Stack

	prop loglevel
	prop nodes
	prop scopes
	prop root
	prop state
	prop meta
	prop theme
	prop css

	def initialize
		reset

	def reset
		@nodes    = []
		@scoping  = []
		@scopes   = []
		@stash    = Stash.new(self)
		@loglevel = 3
		@counter  = 0
		@counters = {}
		@options = {}
		@state = {}
		@tag = null
		@sourceId = null
		@symbols = {}
		@css = StyleSheet.new(self)
		@theme = null
		@meta = {}
		# @css = ''
		@runtime
		MPREV = [-1,-1,-1]
		self

	def runtime
		@root.runtime

	def corelib
		@root.importProxy('core','imba/runtime','').proxy

	def cssns
		@root.cssns

	def use item
		@root.use(item)

	def incr name
		@counters[name] ||= 0
		@counters[name] += 1

	def decr name
		@counters[name] ||= 0
		@counters[name] -= 1

	def strip val
		SourceMapper.strip(val)

	def generateId ns = 'oid'
		return AST.counterToShortRef(STACK.tsc ? 1 : STACK.incr(ns))
		AST.counterToShortRef(STACK.incr(ns))

	def getSymbol ref, alias = null, name = ''
		let key = ref or (STACK.tsc ? 1 : incr('symbols'))
		# ref ||= "" + incr('symbols')
		# Belongs in root
		@symbols[key] ||= @root.declare((alias or ref),LIT("Symbol({name ? helpers.singlequote(name) : ''})"),system: yes, alias: (alias or ref)).resolve.c


	def symbolFor name
		@root.symbolRef(name)

	def imbaSymbol name
		STACK.isStdLib ? symbolFor('#' + name) : corelib[name + '$']

	def toInternalName name
		let base = name
		if name:c isa Function
			name = name.c

		let str = "Ω" + strip(name).split(".").join("__")
		let nr = incr(str)
		if nr > 1
			str += "Ω" + nr


		# Include something for sourcemapping?
		return str

	def toInternalClassName name
		
		if name:toClassName
			name = name.toClassName
		elif name:c isa Function
			name = name.c

		let stripped = strip(name)
		let str = "Ω" + strip(name).split(".").join("__")
		let nr = incr(str)
		if nr > 1
			str += "Ω" + nr

		return str

	def domCall name
		if true
			name = {
				start: 'beforeVisit'
				end: 'afterVisit'
				open: 'beforeReconcile'
				close: 'afterReconcile'
				insert: 'placeChild'
			}[name] or name

			"[{symbolFor('#' + name)}]"
		else
			".{name}$"

	def sourceId
		return @sourceId if (@sourceId ||= @options:sourceId)
		let src = sourcePath
		let cwd = self.cwd
		# relativize the cwd thing
		# TODO rename+document this option. sourceBase or sourceRoot?
		if @options:path and cwd
			src = @options:path.relative(cwd,src)

		unless src
			throw Error.new("Include sourceId or sourcePath in options compile(code,options)")

		@sourceId = helpers.identifierForPath(src)
		return @sourceId

	def theme
		@theme ||= StyleTheme.wrap(@options:config)

	def stash
		@stash

	def set obj
		@options ||= {}
		for own k,v of obj
			@options[k] = v
		self

	# get and set
	def option key, val
		if val != undefined
			@options ||= {}
			@options[key] = val
			return self

		@options && @options[key]

	def platform
		@options:platform or 'browser'
	
	def mode
		@options:mode or 'production'

	def format
		@options:format

	def sourcePath
		@options:sourcePath

	def imbaPath
		@options:imbaPath

	def resolveColors
		@options:styles !== 'extern' or @options:resolveColors

	def config
		@options:config or {}

	def cwd
		config && config:cwd

	def tsc
		platform == 'tsc' or @options:tsc

	def hmr
		!!@options:hmr

	def isStdLib
		!!@options:stdlib

	def isWeb
		platform == 'browser' or platform == 'web'

	def isWorker
		platform == 'worker'

	def isNode
		platform == 'node'

	def isDev
		mode == 'development'

	def isProd
		mode == 'production'

	def isVite
		!!@options:vite

	def env key
		var val = @options["ENV_{key}"]
		return val if val != undefined

		if F[key] !== undefined
			return F[key]

		var lowercased = key.toLowerCase

		if @options[lowercased] != undefined
			return @options[lowercased]

		if key == 'VITE'
			@meta:universal = no
			return isVite

		elif key == 'WEB' or key == 'BROWSER'
			@meta:universal = no
			return isWeb
		elif key == 'NODE'
			@meta:universal = no
			return isNode
		elif key == 'DEV'
			return isDev
		elif key == 'PROD'
			return isProd
		elif key == 'NODEISH'
			@meta:universal = no
			return isNode or !!tsc

		elif key == 'TSC'
			return self.tsc

		elif key == 'WORKER'
			@meta:universal = no
			return platform and platform.indexOf('worker') >= 0
			
		elif key == 'WEBWORKER'
			@meta:universal = no
			return platform == 'webworker'

		elif key == 'HMR'
			return !!@options:hmr

		if var e = @options:env
			if e.hasOwnProperty(key)
				return e[key]
			elif e.hasOwnProperty(key.toLowerCase)
				return e[key.toLowerCase]

		if $node$ and typeof process != 'undefined' and process:env
			val = process:env[key.toUpperCase]
			if val != undefined
				return val
			return null

		return undefined	

	def addScope scope
		@scopes.push(scope)
		self

	def traverse node
		self

	def push node
		@nodes.push(node)
		# not sure if we have already defined a scope?
		self

	def pop node
		@nodes.pop # (node)
		self

	def call ctx, cb
		let prev = CONTEXT
		CONTEXT = ctx
		let res = cb()
		CONTEXT = prev
		return res

	get is_top_level
		@nodes:length < 4

	def parent
		@nodes[@nodes:length - 2]

	def current
		@nodes[@nodes:length - 1]

	def indexOf test
		let res = up(test)
		res ? @nodes.indexOf(res) : -1

	def up test
		test ||= do |v| !(v isa VarOrAccess)

		if typeof test == 'number'	
			return @nodes[@nodes:length - (1 + test)]

		var i = @nodes:length - 2

		if test:prototype isa Node
			while i >= 0
				var node = @nodes[i--]
				return node if node isa test
			return null

		while i >= 0
			var node = @nodes[i]
			return node if test(node)
			i -= 1
		return null

	def parents test
		test ||= do |v| !(v isa VarOrAccess)
		if test:prototype isa Node
			let cls = test
			test = do |v| v isa cls

		@nodes.filter(test)

	def relative node, offset = 0
		var idx = @nodes.indexOf(node)
		idx >= 0 ? @nodes[idx + offset] : null

	def scope lvl = 0
		return @withScope if @withScope
		var i = @nodes:length - 1 - lvl
		while i >= 0
			var node = @nodes[i]
			return node.@scope if node.@scope
			i -= 1
		return null

	def withScope scop, cb
		let prev = @withScope
		@withScope = scop
		cb()
		@withScope = prev
		return

	def scopes
		# include deeper scopes as well?
		var scopes = []
		var i = @nodes:length - 1
		while i >= 0
			var node = @nodes[i]
			scopes.push(node.@scope) if node.@scope
			i -= 1
		return scopes

	def closure
		scope.closure

	def closures
		scopes.filter do |scope| scope.closure == scope

	def method
		up(MethodDeclaration)

	def block
		up(Block)

	def blockpart
		let i = @nodes:length - 1
		while i
			if @nodes[i - 1] isa Block
				return @nodes[i]
			i--
		return

	def prependInBlock node
		block.add([node,BR], before: blockpart)

	def lastImport
		let scopes = self.scopes
		for scope in scopes
			if scope.@lastImport
				return scope.@lastImport
		return null

	def isExpression
		var i = @nodes:length - 1
		while i >= 0
			var node = @nodes[i]
			# why are we not using isExpression here as well?
			if node isa Code or node isa Loop or node.isStatementLike
				return false
			if node.isExpression
				return true
			# probably not the right test - need to be more explicit
			i -= 1
		return false

	def toString
		"Stack({@nodes.join(" -> ")})"

	def isAnalyzing
		@analyzing

	def scoping
		@nodes.filter(|n| n.@scope ).map(|n| n.@scope )

	def currentRegion
		let l = @nodes:length
		let node = @nodes[--l]
		node and [node.startLoc,node.endLoc]

# Lots of globals -- really need to deal with one stack per file / context
export var STACK = Stack.new

# use a bitmask for these

export class Node

	prop o
	prop options
	prop traversed

	# reference to the script object this node
	# is part of
	def script
		# TODO don't use global state for this
		Compilation:current

	def safechain
		no

	def addEnv env
		@envs ||= []
		@envs.push( EnvFlag.new env)
		self

	def isExcluded
		no

	def sourcecode
		let src = STACK.SOURCECODE
		let start = startLoc
		let end = endLoc
		src.slice(start,end)

	def oid
		@oid ||= STACK.generateId('')

	def tid
		@tid ||= STACK.generateId('tag')

	def osym ns = '',name = ''
		STACK.getSymbol(oid + ns,null,name)

	def symbolRef name
		STACK.root.symbolRef(name)

	def domCall name
		STACK.domCall(name)

	# get global symbol with name
	def gsym name
		STACK.root.symbolRef(name)

	def sourceId
		STACK.sourceId

	# shorthand for the self context for a node
	def slf
		scope__.context

	def p
		# allow controlling this from CLI
		if STACK.loglevel > 0
			console.log(*arguments)
		self

	def runtime
		STACK.runtime

	def typeName
		self:constructor:name

	def namepath
		typeName

	def initialize
		setup
		self

	def setup
		@expression = no
		@traversed = no
		@parens = no
		@cache = null
		@value = null
		self

	def setStartLoc loc
		@startLoc = loc
		return self

	def setEndLoc loc
		@endLoc = loc
		return self

	def setRegion loc
		if loc isa Node
			loc = loc.region

		if loc isa Array
			@startLoc = loc[0]
			@endLoc = loc[1]
		return self

	def setEnds start,end
		if end and end:endLoc
			@endLoc = end.endLoc
		if start and start:startLoc
			@startLoc = start.startLoc
		self

	def startLoc
		@startLoc

	def endLoc
		@endLoc

	def set obj
		@options ||= {}
		for own k,v of obj
			@options[k] = v
		self

	# get and set
	def option key, val
		if val != undefined
			@options ||= {}
			@options[key] = val
			return self

		@options && @options[key]

	def o
		@options ||= {}

	def keyword
		@keyword or (@options and @options:keyword)

	def datatype
		@options ? @options:datatype : null

	def datatype= val
		option('datatype',val)

	def configure obj
		set(obj)

	def region
		[0,0]

	def loc
		[startLoc or 0,endLoc or 0]

	def token
		null

	def compile
		self

	def visit
		self

	def stack
		STACK

	def isString
		no

	def isPrimitive deep
		no

	def isReserved
		no

	def isGlobal name
		no

	def isConstant
		no

	# should rather do traversals
	# o = {}, up, key, index
	def traverse o
		if @traversed
			return self
		# NODES.push(self)
		@traversed = yes
		let prev
		if o
			prev = STACK.state
			STACK.state = o
		STACK.push self
		visit(STACK,STACK.state)
		STACK.pop self
		if o
			STACK.state = prev
		return self

	def inspect
		{type: self:constructor.toString}

	def js o
		"NODE"

	def toString
		"{self:constructor:name}"

	# swallow might be better name
	def consume node
		if node isa TagLike
			return node.register(self)

		if node isa PushAssign
			node.register(self)
			return PushAssign.new(node.op,node.left,self)

		if node isa Assign
			# node.right = self
			return OP(node.op,node.left,self)
		elif node isa VarDeclaration
			return OP('=',node.left,self)
		elif node isa Op
			return OP(node.op,node.left,self)
		elif node isa Return
			return Return.new(self)
		elif node == NumberLike
			return NumberLike.new(self)
		elif node isa Util.Is
			return node.clone(self)
		elif node isa AmperWalker
			node.test(self)

		return self

	def toExpression
		@expression = true
		self

	def forceExpression
		@expression = true
		self

	def isExpressable
		true

	def isExpression
		@expression || false

	def isStatementLike
		false

	def isRuntimeReference
		no

	def hasSideEffects
		true

	def isUsed
		true

	def shouldParenthesize
		false

	def shouldParenthesizeInTernary
		yes

	def block
		Block.wrap([self])

	def node
		self

	def unwrappedNode
		self

	def scope__
		STACK.scope

	def up
		STACK.parent

	def util
		Util

	def receiver
		self

	def indented a,b

		if a isa Indentation
			@indentation = a
			return self

		# this is a _BIG_ hack
		if b isa Array
			add(b[0])
			b = b[1]

		# if indent and indent.match(/\:/)
		@indentation ||= a and b ? Indentation.new(a,b) : INDENT
		self

	def prebreak term = '\n'
		self

	def invert
		return OP('!',self)

	def cache o = {}
		@cache = o
		o:var = (o:scope or scope__).temporary(self,o)
		o:lookups = 0
		self

	def cachevar
		@cache && @cache:var

	def decache
		if @cache
			cachevar.free
			@cache = null
		self

	# the "name-suggestion" for nodes if they need to be cached
	def alias
		null

	# Shorthand for outputting sourcemapped keywords where it looks
	# for an option with the same name in options
	def mo val,optional = no
		let src = @options and @options[val]
		optional and !src ? '' : M(val,src)

	def warn message, opts = {}
		let loc = opts:loc or self.loc or [0,0]

		if loc isa Node
			loc = [loc.startLoc,loc.endLoc]

		if loc isa Token
			loc = loc.loc

		# if loc[0] == 0 and loc[1] == 0

		# console.log 'loc warn',loc,script.rangeAt(loc[0],loc[1])
		script.addDiagnostic(opts:severity or 'warning',{
			message: message
			range: script.rangeAt(loc[0],loc[1])
		})

		# Compilation.warn(
		#	severity: opts:severity or 'warning'
		#	message: message
		#	offset: (loc ? loc[0] : 0)
		#	length: (loc ? (loc[1] - loc[0]) : 0)
		# )

	def error message, opts = {}
		opts:severity = 'error'
		warn(message,opts)

	def c o
		var s = STACK
		var ch = @cache
		return c_cached(ch) if ch and ch:cached

		s.push(self)
		forceExpression if o && o:expression

		if o and o:indent
			@indentation ||= INDENT

		var out = js(s,o)

		var paren = shouldParenthesize

		s.pop(self)

		if out == undefined
			return out

		if var indent = @indentation
			out = indent.wrap(out,o)
			self

		# should move this somewhere else really
		out = "({out})" if paren
		if (o and o:braces) or (@options and @options:braces)
			if indent
				out = '{' + out + '}'
			else
				out = '{ ' + out + ' }'

		if ch = @cache
			out = "{ch:var.c} = {out}" unless ch:manual
			var par = s.current
			par = par.node if par isa ValueNode
			out = '(' + out + ')' if par isa Access || par isa Op # others? #
			ch:cached = yes

		if OPTS:sourcemap and (!o or o:mark !== false)
			out = M(out,self)
		return out

	def c_cached cache
		cache:lookups++
		cache:var.free if cache:uses == cache:lookups
		return cache:var.c # recompile every time??

export class ValueNode < Node

	prop value

	def startLoc
		let loc = @startLoc
		(typeof loc == 'number') ? loc : (@value and @value:startLoc ? @value.startLoc : -1)

	def initialize value
		setup
		@value = load(value)

	def load value
		value

	def js o
		typeof @value == 'string' ? @value : @value.c

	def visit

		@value.traverse if @value isa Node #  && @value:traverse
		self

	def region
		[@value.@loc,@value.@loc + @value.@len]

export class ValueReferenceNode < Node
	prop value
	prop orig

	def initialize value, orig
		setup
		@value = value
		@orig = orig or value

	def startLoc
		@orig?.startLoc

	def endLoc
		@orig?.endLoc

	def load value
		value

	def js o
		let res = M(@value.c(mark: false),self)
		return res

	def visit
		@value.traverse if @value isa Node #  && @value:traverse
		self

	def region
		[@orig.@loc,@orig.@loc + @orig.@len]

export class ExpressionNode < ValueNode

export class AssertionNode < ValueNode

	def sourceFor node
		let src = STACK.SOURCECODE
		let [start, end] = node.loc
		JSON.stringify(src.slice(start,end))

	def isInspectableBinary op
		op isa Op and !(op isa Access) and !(op isa UnaryOp) and !op.isLogical and !op.isAssignment and op.left and op.right

	def js o
		let op = @value
		let out = []

		if isInspectableBinary(op)
			let l = op.left
			let r = op.right
			let osrc = sourceFor(op)
			let lsrc = sourceFor(l)
			let rsrc = sourceFor(r)
			let oval = JSON.stringify(op.@op)
			let lval = l.cache.c(o)
			let rval = r.cache.c(o)

			out = [
				"source:{osrc}",
				"operator:{oval}",
				"left:\{source:{lsrc},value:{lval}\}",
				"right:\{source:{rsrc},value:{rval}\}"
			]
			let meta = out.join(',')
			out = ["globalThis.IMBA_ASSERT=\{type:'binary',{meta}\}"]
			out.push(op.c(o))
		else
			let osrc = sourceFor(op)
			let oval = op.cache.c(o)
			out.push("globalThis.IMBA_ASSERT=\{type:'expression',source:{osrc},value:{oval}\}")
			out.push(op.c(o))
		return '(' + out.join(',') + ")" # ,{op.c(o)})
		# "('assert',{super})"

export class Statement < ValueNode

	def isExpressable
		return no

export class Meta < ValueNode

	def isPrimitive deep
		yes

export class Comment < Meta

	def visit
		if var block = up
			var idx = block.indexOf(self) + 1
			idx += 1 if block.index(idx) isa Terminator
			if var next = block.index(idx)
				next.@desc = self unless toString.match(/@(overload|ts)\b/)
		self

	def isMultiline
		@value.type == 'HERECOMMENT'

	def toDoc
		helpers.normalizeIndentation("" + @value.@value)

	def toJSON
		helpers.normalizeIndentation("" + @value.@value)

	def toString
		@value.@value

	def c o
		return "" if STACK.option(:comments) == false or @skip
		var v = @value.@value
		var out = ""

		# Temporary way to support defining raw typescript types in comments.
		# Whenever a multiline comment starts with @ts - just let it through
		# to the output directly.
		if STACK.tsc and v.indexOf(' @ts\n') == 0
			return v.slice(5)

		if o and o:expression or v.match(/\n/) or isMultiline
			v = v.replace(/\*\//g, '\\*\\/').replace(/\/\*/g, '\\/\\*')
			v = '*' + v if v.match(/\@(type|param|satisfies|template)/) or STACK.tsc

			out += "/*{v}*/"
		elif v.match(/\@(type|param|satisfies|template)/)
			out += "/** {v} */"
		else
			out += "// {v}"
		
		return out

export class Terminator < Meta

	def initialize v
		@value = v
		self

	def traverse
		self

	def loc
		[@value.@loc,@value.@loc + @value.@value:length]

	def startLoc
		@value:startLoc ? @value.startLoc : -1

	def endLoc
		@value.@value ? (startLoc + @value.@value:length) : -1

	def c
		let val = @value.c

		if STACK.option(:comments) == false
			val = val.replace(/\/\/.*$/gm,'')

		if STACK.tsc
			# make comments significant for tooling
			# temporary hack to work around parsing issue with reference path
			val = val.replace(/\/{3}/g,'~~/~~')
			val = val.replace(/\/\/\s(.*)$/gm,'/** $1 */ ')
			val = val.replace(/\~\~\/\~\~/g,'///')

		if STACK.tsc and (val:length > 1 or @first)
			return M(val.replace(/^[\t ]+/gm,''),self)

		return val.replace(/^[\t ]+/gm,'')

export class Newline < Terminator

	def initialize v
		@traversed = no
		@value = v or '\n'

	def c
		@value
		# M(@value,@value)

# weird place?
export class Index < ValueNode

	def startLoc
		@startLoc or @value?.startLoc

	def endLoc
		@endLoc or @value?.endLoc

	def cache o = {}
		@value.cache(o)

	def js o
		@value.c

export class ListNode < Node

	prop nodes

	def consume node
		if node isa Walker
			for n in @nodes
				n.consume(node)
			return self
		super

	def initialize list
		setup
		@nodes = load(list == null ? [] : list)
		@indentation = null

	# PERF acces @nodes directly?
	def list
		@nodes

	def compact
		@nodes = AST.compact(@nodes)
		self

	def load list
		list

	def concat other
		# need to store indented content as well?
		@nodes = nodes.concat(other isa Array ? other : other.nodes)
		self

	def swap item, other
		var idx = indexOf(item)
		nodes[idx] = other if idx >= 0
		self

	def push item
		@nodes.push(item)
		self

	def pop
		var end = @nodes.pop
		return end

	def add item, o
		let idx = null
		if o and o:before
			idx = @nodes.indexOf(o:before)
			idx = null if idx == -1
		elif o and o:after
			idx = @nodes.indexOf(o:after) + 1
			idx = null if idx == 0
			if idx >= 1
				while @nodes[idx] isa Meta
					idx++
		elif o isa Number
			idx = o

		if idx !== null
			item isa Array ? @nodes.splice(idx,0,*item) : @nodes.splice(idx,0,item)
		else
			item isa Array ? @nodes.push(*item) : @nodes.push(item)
		self

	def unshift item, br
		@nodes.unshift(BR) if br
		@nodes.unshift(item)
		self

	# test
	def slice a, b
		self:constructor.new(@nodes.slice(a,b))

	def break br, pre = no
		br = Terminator.new(br) if typeof br == 'string'
		pre ? unshift(br) : push(br)
		self

	def some cb
		for node in @nodes
			return yes if cb(node)
		return no

	def every cb
		for node in @nodes
			return no unless cb(node)
		return yes

	# filtered list of items
	def values
		@nodes.filter do |item| !(item isa Meta)

	def filter cb
		@nodes.filter(cb)

	def pluck cb
		var item = filter(cb)[0]
		remove(item) if item
		return item

	def indexOf item
		@nodes.indexOf(item)

	def index i
		@nodes[i]

	def remove item
		var idx = @nodes.indexOf(item)
		@nodes.splice(idx, 1) if idx >= 0
		self

	def removeAt idx
		var item = @nodes[idx]
		@nodes.splice(idx, 1) if idx >= 0
		return item

	def replace original, replacement
		var idx = @nodes.indexOf(original)
		if idx >= 0
			if replacement isa Array
				@nodes.splice(idx,1,*replacement)
			else
				@nodes[idx] = replacement
		self

	def first
		@nodes[0]

	def last
		var i = @nodes:length
		while i
			i = i - 1
			var v = @nodes[i]
			return v unless v isa Meta
		return null

	def map fn
		@nodes.map(fn)

	def forEach fn
		@nodes.forEach(fn)

	def remap fn
		@nodes = map(fn)
		self

	def count
		@nodes:length

	def len
		@nodes:length

	def realCount
		var k = 0
		for node in @nodes
			k++ if node and !(node isa Meta)
		return k

	def isEmpty
		realCount == 0

	def visit
		let items = @nodes
		let i = 0

		while i < items:length
			let item = items[i]
			if item:traverse
				let res = item.traverse
				if res != item
					if res isa Array
						items.splice(i,1,*res)
						continue
			i++
		self

	def isExpressable
		for node in nodes
			return no if node and !node.isExpressable

		return yes

	def toArray
		@nodes

	def delimiter
		@delimiter or ","

	def js o, nodes: @nodes
		var delim = ','
		var express = delim != ';'
		var last = last

		var i = 0
		var l = nodes:length
		var str = ""

		for arg in nodes
			var part = typeof arg == 'string' ? arg : (arg ? arg.c(expression: express) : '')

			str += part
			str += delim if part and (!express or arg != last) and !(arg isa Meta)

		return str

	def indented a,b
		if a isa Indentation
			@indentation = a
			return self

		@indentation ||= a and b ? Indentation.new(a,b) : INDENT
		self

	def endLoc
		if @endLoc
			return @endLoc

		var i = @nodes:length
		let last = @nodes[i - 1]
		last?.endLoc

export class ArgList < ListNode

	def startLoc
		if typeof @startLoc == 'number'
			return @startLoc
		first?.startLoc

	def consume node
		if node isa TagLike
			@nodes = @nodes.map do |child|
				if !(child isa Meta) # and !(child isa Assign)
					child.consume(node)
				else
					child
			return self
		super

	def setEnds start,end
		@generated = start and start:generated
		if end and end:endLoc and end.endLoc != -1
			@endLoc = end.endLoc
		if start and start:startLoc and start.startLoc != -1
			@startLoc = start.startLoc
		self

export class AssignList < ArgList

	def concat other
		if @nodes:length == 0 and other isa AssignList
			return other
		else
			super(other)
		# need to store indented content as well?
		# @nodes = nodes.concat(other isa Array ? other : other.nodes)
		self

export class Block < ListNode

	prop head

	def initialize list
		setup
		@nodes = list or []
		@head = null
		@indentation = null

	def startLoc
		@indentation ? @indentation.startLoc : super

	def endLoc
		@indentation ? @indentation.endLoc : super

	def self.wrap ary
		unless ary isa Array
			throw SyntaxError.new("what")
		ary:length == 1 && ary[0] isa Block ? ary[0] : Block.new(ary)

	def visit stack
		@scope.visit if @scope

		if stack and stack.@tag
			@tag = stack.@tag

		@traversing = true
		for node,i in @nodes.slice(0)
			node and node.traverse
		@traversing = false

		self

	def block
		self

	def collectDecorators
		if let decorators = @decorators
			@decorators = null
			return decorators
		return null

	def loc
		# rather indents, no?
		if var opt = option(:ends)
			var a = opt[0].loc
			var b = opt[1].loc

			p "no loc for {opt[0]}" unless a
			p "no loc for {opt[1]}" unless b

			return [a[0],b[1]]

		if var ind = @indentation
			if ind.aloc != -1
				return [ind.aloc,ind.bloc]

		let a = @nodes[0]
		let b = @nodes[@nodes:length - 1]

		[a and a.loc[0] or 0,b and b.loc[1] or 0]

	# go through children and unwrap inner nodes
	def unwrap
		var ary = []
		for node,i in nodes
			if node isa Block
				ary:push.apply(ary,node.unwrap)
			else
				ary.push(node)
		return ary

	# This is just to work as an inplace replacement of nodes.coffee
	# After things are working okay we'll do bigger refactorings
	def compile o = {}
		var root = Root.new(self,o)
		root.compile(o)

	# Not sure if we should create a separate block?
	def analyze o = {}
		self

	def cpart node
		return "" if node === BR0
		var out = typeof node == 'string' ? node : (node ? node.c : "")
		return "" if out == null or out == undefined or out == ""

		if out isa Array
			var str = ""
			var l = out:length
			var i = 0
			while i < l
				str += cpart(out[i++])
			return str

		var hasSemiColon = SEMICOLON_TEST.test(out)
		out += delimiter unless hasSemiColon or node isa Meta
		return out

	def delimiter
		@delimiter == undefined ? ';' : @delimiter

	def js o, opts
		var ast = @nodes
		var l = ast:length
		# really?
		var express = isExpression or o.isExpression or (option(:express) and isExpressable)
		return '' if ast:length == 0 and (!@head or @head:length == 0)

		if express
			return super(o,nodes: ast)

		var str = ""
		let empty = no
		for v in ast
			let vs = cpart(v)
			# FIXME windows?
			if vs[0] == '\n' and (/^\n+$/).test(vs)
				continue if empty
				empty = yes
			elif vs
				empty = no
			# console.log 'add item?',JSON.stringify(vs)
			str += vs

		# now add the head items as well
		if @head and @head:length > 0
			var prefix = ""
			for v in @head
				var hv = cpart(v)
				prefix += hv + '\n' if hv
			str = prefix + str

		if option(:strict)
			str = cpart('"use strict";\n') + str

		return str

	# Should this create the function as well?
	def defers original, replacement
		var idx = @nodes.indexOf(original)
		@nodes[idx] = replacement if idx >= 0
		var rest = @nodes.splice(idx + 1)
		return rest

	def expressions
		var expressions = []
		for node in nodes
			expressions.push(node) unless node isa Terminator
		return expressions

	def consume node
		if node isa TagLike
			let real = expressions

			@nodes = @nodes.map do |child|
				if child in real and !(child isa Assign)
					child.consume(node)
				else
					child
			return self

		# can also return super if it is expressable, but should we really?
		if var before = last
			var after = before.consume(node)
			if after != before
				if after isa Block
					after = after.nodes

				replace(before,after)

		return self

	def isExpressable
		return no unless @nodes.every(|v| v.isExpressable )
		return yes

	def isExpression

		option(:express) || @expression

	def shouldParenthesizeInTernary
		if count == 1
			return first.shouldParenthesizeInTernary

		yes

	def indented a,b
		super
		if a isa Token and a.@type == 'INDENT'
			if let post = (a.@meta and a.@meta:post)
				a.@meta:post = post.trim + '\n'
				
		return self

class ClassInitBlock < Block

	def c o
		let out = super
		if @nodes:length > 1
			return 'static {\n' + helpers.indent(out) + '\n}'
		else
			return 'static { ' + out.replace(/\;$/,'') + ' }'

class InstanceInitBlock < Block

class InstancePatchBlock < InstanceInitBlock

export class ClassField < Node
	prop name

	def initialize name
		super
		@name = name

	def isExcluded
		if STACK.tsc
			return @envs and @envs.find(do $1.@key == 'JS') or false

		if @envs
			return !@envs.find(do $1.isTruthy)
		return false

	def visit
		return if isExcluded
		@decorators = up?.collectDecorators
		@classdecl = STACK.up(ClassDeclaration)
		@name.traverse if @name and @name:traverse

		if value
			value.@scope = @vscope = FieldScope.new(value)
			value.@scope.@parent = scope__
			
			value.traverse

		if watchBody
			@descriptor = STACK.root.declare("{oid}$Prop",util.watcher(storageSymbol,watcherSymbol), type: 'const', system: yes)

		if wrapper
			# worth of a full function
			# dont add these in tsc?
			@vslot = osym('slot',String(@name))
			@fslot = osym('meta')
			@fname = @name.metaIdentifier

			wrapper.@scope = @vscope = FieldScope.new(wrapper)
			wrapper.@scope.@parent = scope__
			wrapper.traverse
		self

	def value
		option(:value)

	def target
		option(:static) ? LIT('this') : LIT('this.prototype')

	def storageSymbol
		symbolRef("#{name.c(as:'symbolpart')}")

	def watcherSymbol
		symbolRef("#{name.c(as:'symbolpart')}DidSet")

	def storageKey
		@storageKey ||= STR(name.c + '$$')

	def storageMap
		@storageMap ||= scope__.root.declare(null,LIT('new WeakMap()'))

	def isPlain
		!@decorators and (!@value or @value.isPrimitive)

	def isMember
		!option(:static)

	def isLazy
		no

	def hasStaticInits
		isStatic or @decorators # or watchBody

	def hasConstructorInits
		!isStatic

	def isStatic
		option(:static)

	def watchBody
		option(:watch)

	def wrapper
		option(:wrapper)

	def loc
		[@name.@loc,@name.region[1]]

	def c
		return if option(:struct)
		return if isExcluded

		let up = STACK.current
		let tsc = STACK.tsc
		let out

		if up isa ClassBody
			# return if isPlain
			let prefix = isStatic ? "{M('static',option(:static))} " : ''
			let name = name isa IdentifierExpression ? name.asObjectKey : name.c(as: 'field')
			let cls = STACK.up(ClassDeclaration)
			let typ = STACK.tsc and datatype

			if wrapper
				let meta = @metaname = @name.metaIdentifier
				let slot = @vslot
				let metasym = @fslot
				let inner
				let context = null

				if isStatic
					context = cls.classReference.c
				else
					context = "{cls.classReference.c}.prototype"

				let op = OP('.',LIT('this'),meta)
				let args = "this,{slot},{@name.c(as: 'value')}"
				@getter = LIT("()\{ return {op.c}.$get({args}) \}")
				@setter = LIT("(val)\{ {op.c}.$set(val,{args}) \}")

				if tsc
					@getter = LIT("():ReturnType<typeof {op.c}.$get> \{ return {op.c}.$get({args}) \}")
					@setter = LIT("(val:Parameters<typeof {op.c}.$set>[0])\{ {op.c}.$set(val,{args}) \}")
					
					let pars = [wrapper.c(expression: yes),args,metasym,context]
					let extending = cls.option('extension')
					
					let slf = "const self = this"

					if extending
						let clsname = cls.@className
						let iface = null
						if clsname isa Identifier and !clsname.@variable
							# Identifier should not have this much responsibility - it should be wrapped in an VarOrAccess?
							iface = GLOBAL_INTERFACES[clsname.@value]

						if clsname

							let gen = clsname and clsname.option(:generics)
							let suptyp = clsname.c

							if gen
								suptyp += String(gen)

							# slf = (iface and iface:thistype ? LIT("const self = null as any as {iface:thistype}") : LIT("const self = this as (this & {suptyp})"))

							slf = (iface and iface:thistype ? LIT("const self = null as any as {iface:thistype}") : LIT("const self = this as unknown as ({suptyp})"))
					# const slf = {context};
					inner = "{slf};return {runtime:accessor}({pars.join(',')})"

					if wrapper.@callback
						# set self value for this?
						inner += '.$function(' + wrapper.@callback.c + ')'

				else
					inner = "return this[{metasym}] || {runtime:accessor}({wrapper.c(expression: yes)},{args},{metasym},{context})"
					# inner = STACK.tsc ? "return {inner}" : "return this[{metasym}] || {inner}"
				@handler = LIT("{M(meta.c(as: 'field'),@name)}()\{ {inner} \}")

			if tsc
				let vars = {
					name: @name
					static: option(:static)
					declare: option(:declareOnly)
					protected: option(:protected)
					type: datatype
					value: value
				}
				let tpl
				if wrapper
					# vars:getter = self.getter.c(keyword: '')
					# vars:setter = self.setter.c(keyword: '')

					let getter = "{mo('protected',yes)} {prefix}get {M(@name)}{self.getter.c(keyword: '')}"
					let setter = "{mo('protected',yes)} {prefix}set {M(@name)}{self.setter.c(keyword: '')}"
					# console.log @name,setter,getter,name
					if typ
						getter = "{getter}:{typ.c}"

					out = "{getter}\n{setter}\n{prefix}get {@handler.c}"

					# if !isStatic
					# 	# how would this fare with class extensions?
					# 	out += "\nstatic get {M(@metaname.c(as: 'field'),@name)}()\{ return {OP('.',LIT('this.prototype'),@metaname).c} \}"

					return out

				elif self isa ClassAttribute or (@decorators and @decorators:length)
					let sym = osym
					out = "declare {prefix} {M(name,@name)}"
					out = "{out}:{C(typ)}" if typ
					return out

				else
					tpl = '@declare @protected @static @name@{:@type} @{ = @value}'
					out = "{prefix}{M(name,@name)}" # the value scope?
					out += ":{C(typ)}" if typ
					out += " = {value.c}" if value

				if tpl
					return TPL(vars,tpl)


			elif self isa ClassAttribute or (@decorators && @decorators:length > 0 and false) or wrapper
				let setter = "{prefix}set {name}{self.setter.c(keyword: '')}"
				let getter = "{prefix}get {name}{self.getter.c(keyword: '')}"
				out = "{setter}\n{getter}"

				if wrapper
					# let wr = "{runtime:property}({wrapper.c}).accessor()"
					out += "\n{prefix}get {@handler.c}"

			return out

		return if STACK.tsc

		if isStatic and up isa ClassInitBlock
			if @vscope
				if let fn = STACK.up(Func)
					@vscope.mergeScopeInto(fn.@scope)
			out = OP('=',OP('.',THIS,name),value or UNDEFINED).c + ';\n'

		elif !isStatic and up isa ClassInitBlock
			return ""

		elif !isStatic and up isa InstanceInitBlock
			if @vscope
				if let fn = STACK.up(Func)
					@vscope.mergeScopeInto(fn.@scope)

			let key = name
			if name isa Identifier
				key = name.toStr

			let ctor = up.option(:ctor)
			let opts = up.option(:opts)
			let val = value or UNDEFINED

			let paramIndex = option(:paramIndex)
			let restIndex = option(:restIndex)
			let access
			let rest

			if up isa InstancePatchBlock
				# Dropped the patch block now
				rest = ctor.@params.at(restIndex,yes,'$$',LIT('{}'))
				access = OP('.',rest,name)
				access.cache(reuse: yes, name: 'vsds',safe: yes)

				let right = OP('=',OP('.',THIS,name),access)

				if wrapper
					right = CALL(
						OP('.',OP('.',THIS,@fname),STR('$init')),
						[access,THIS,@vslot,LIT(@name.c(as: 'value')),rest]
					)
				out = OP('&&',OP('!==',access,UNDEFINED),right)

			elif paramIndex != undefined
				let name = option(:paramName)
				access = ctor.@params.at(paramIndex,yes,name)
				if value
					val = If.ternary(OP('!==',access,UNDEFINED),access,val)
				else
					val = access

			elif restIndex != undefined
				rest = ctor.@params.at(restIndex,yes,'$$',LIT('null'))
				access = OP('.',rest,name)

				if value
					access.cache(reuse: yes, name: 'v', safe: yes)
					val = If.ternary(OP('&&',rest,OP('!==',access,UNDEFINED)),access,val)
				else
					val = If.ternary(rest,access,UNDEFINED)

			if self isa ClassAttribute and !value
				return

			if wrapper
				unless up isa InstancePatchBlock
					out = CALL(
						OP('.',OP('.',THIS,@fname),STR('$init')),
						[val,THIS,@vslot,LIT(@name.c(as: 'value')),rest]
					)
				out = OP('&&',LIT('fields'),out)

			out ||= OP('=',OP('.',THIS,name),val)
			out = out.c + ';\n'

			if watchBody
				@descriptor ||= STACK.root.declare("{oid}$Prop",util.watcher(storageSymbol,watcherSymbol), type: 'const', system: yes)
				out = "Object.defineProperty(this,{key.c},{@descriptor.c});\n{out}"

		return out

	def getter
		@getter ||= if true
			if wrapper
				LIT("()\{ return this.__{name.c}.$get(this,{name.toStr.c},{osym}) \}")
			else
				parseTemplate('(){ return $get$; }')

	def setterForValue value
		OP('=',OP('.',THIS,storageKey),value)

	def parseTemplate tpl
		tpl = tpl.replace(/\$(\w+)\$/g) do |m,key|
			if key == 'get'
				GET(THIS,storageSymbol).c
			elif key == 'name'
				name.c

			elif key == 'set'
				OP('=',GET(THIS,storageSymbol),LIT('value')).c
			elif key == 'watcher'
				GET(THIS,watcherSymbol).c
			else
				''
		LIT(tpl)

	def setter
		@setter ||= parseTemplate('(value){ $set$; }')

	def decorater
		@decorater ||= if true
			util.decorate(Arr.new(@decorators),target,name,LIT('null'))

export class ClassProperty < ClassField

export class ClassAttribute < ClassField

	def hasConstructorInits
		!isStatic and value

	def getter
		@getter ||= if true
			let op = CALL(GET(THIS,'getAttribute'),[name.toAttrString])
			FN([],[op])

	def setter
		@setter ||= if true
			let op = CALL(GET(THIS,'setAttribute'),[name.toAttrString,LIT('value')])
			FN([LIT('value')],[op]).set(noreturn: true)

export class ClassRelation < ValueNode

	def c
		""

	def visit
		@classdecl = STACK.up(ClassDeclaration)


export class ClassBody < Block

	def setup
		super
		@fields = []
		@staticFields = []

	def visit stack
		@scope.visit if @scope

		if stack and stack.@tag
			@tag = stack.@tag

		for node,i in @nodes
			if node isa Tag
				# add as render method
				# console.log 'add as render method'
				unless node.tagName == 'self'
					# console.log "cannot use non-self tagname in class"
					let ast = node.@options:type or node
					ast.error "only <self> tag allowed here"

				let meth = MethodDeclaration.new([],[node],Identifier.new('render'),null,{})
				@nodes[i] = node = meth

			node and node.traverse
		self

export class ExpressionList < Block

export class VarDeclList < Block

	def type
		option(:type) or 'var'

	def add part
		push(BR) if @nodes:length

		let node = VarDeclaration.new(part[0],part[1],type).set(decl: self, datatype: part[0].option(:datatype))
		unless @firstDeclaration
			@firstDeclaration = node
			node.set(keyword: keyword)
		push(node)
		return self

	def consume node
		if @nodes:length == 1
			return @nodes[0].consume(node)
		return self

# Could inherit from valueNode
export class Parens < ValueNode

	def initialize value, open, close
		setup
		@open = open
		@close = close
		@value = load(value)

	def unwrappedNode
		@value.unwrappedNode

	def loc
		try
			let a = @open.loc
			let b = @close.loc
			return [a[0],b[1]]
		catch e
			[0,0]

	def endLoc
		@endLoc or (@close and @close:endLoc ? @close.endLoc : 0)

	def load value
		@noparen = no
		value isa Block and value.count == 1 ? value.first : value

	def isString
		# checking if this is an interpolated string
		@open and String(@open) == '("' or value.isString

	def js o

		var par = up
		var v = @value
		var str = null

		@noparen = yes if v isa Func

		if par isa Block
			# is it worth it?
			@noparen = yes unless o.isExpression
			str = v isa Array ? AST.cary(v) : v.c(expression: o.isExpression)
		else
			str = v isa Array ? AST.cary(v) : v.c(expression: yes)

		# check if we really need parens here?
		if datatype and STACK.tsc
			str = '(' + str + '):' + datatype.c
		return str

	def set obj
		# console.log "Parens set {JSON.stringify(obj)}"
		super(obj)

	def shouldParenthesize
		# no need to parenthesize if this is a line in a block
		return no if @noparen #  or par isa ArgList
		return yes

	def prebreak br
		super(br)
		@value.prebreak(br) if @value
		self

	def isExpressable
		@value.isExpressable

	def consume node
		@value.consume(node)

export class PureExpression < Parens

# Could inherit from valueNode
# an explicit expression-block (with parens) is somewhat different
# can be used to return after an expression
export class ExpressionBlock < ListNode

	def c o
		map(|item| item.c(o) ).join(",")

	def consume node
		value.consume(node)

# STATEMENTS

export class Return < Statement

	prop value

	def replace base, replacement
		if @value == base
			@value = replacement

	def initialize v
		@traversed = no
		@value = v isa ArgList and v.count == 1 ? v.last : v
		return self

	def visit
		if @value isa VarReference
			@value.option('virtualize',yes)
		@value.traverse if @value && @value:traverse

	def startLoc
		let l = (keyword or @value)
		l ? l.startLoc : null

	def js o
		var v = @value
		let k = M('return',keyword)

		if v isa ArgList
			return "{k} [{v.c(expression: yes)}]"
		elif v
			return "{k} {v.c(expression: yes)}"
		else
			k

	def c
		if STACK.tsc and value isa This
			return "{M('return',keyword)} {M('this',value)}"

		return super if !value or value.isExpressable

		value.consume(self).c

	def consume node
		return self

export class ImplicitReturn < Return

export class GreedyReturn < ImplicitReturn

export class Yield < Statement

	def visit
		@method = STACK.method or STACK.up(Func)
		@method.set(yield: yes, noreturn: yes)


	def startLoc
		let l = (keyword or @value)
		l ? l.startLoc : null

	def js
		var v = @value
		let k = M('yield',keyword)
		return "{k} {v.c(expression: yes)}"

# cannot live inside an expression(!)
export class Throw < Statement

	def js o
		"throw {value.c}"

	def consume node
		# ROADMAP should possibly consume to the value of throw and then throw?
		return self

export class LoopFlowStatement < Statement

	prop literal
	prop expression

	def initialize lit, expr
		self.literal = lit
		self.expression = expr

	def visit
		expression.traverse if expression

	def consume node
		self

	def c
		return super unless expression
		# get up to the outer loop
		var _loop = STACK.up(Loop)

		# need to fix the grammar for this. Right now it
		# is like a fake call, but should only care about the first argument
		var expr = self.expression

		if _loop.catcher
			expr = expr.consume(_loop.catcher)
			var copy = self:constructor.new(literal)
			Block.new([expr,copy]).c
		elif expr
			var copy = self:constructor.new(literal)
			Block.new([expr,copy]).c
		else
			super
		# return "loopflow"

export class BreakStatement < LoopFlowStatement
	def js o do "break"

export class ContinueStatement < LoopFlowStatement
	def js o do "continue"

export class DebuggerStatement < Statement

	def consume node
		return self

# PARAMS

export class Param < Node

	prop name
	prop index
	prop defaults
	prop splat
	prop variable
	prop value

	def initialize value, defaults, typ
		# could have introduced bugs by moving back to identifier here
		if typeof value == 'string'
			value = Identifier.new(value)

		@traversed = no
		@name = value
		@value = value
		@defaults = defaults
		@typ = typ
		@variable = null

	def varname
		@variable ? @variable.c : name

	def datatype
		super or @value.datatype

	def type
		'param'

	def jsdoc
		let typ = datatype
		if typ && name
			typ.asParam(name)
			# '@param {' + typ.c() + '} ' + name
		else
			''

	def js stack, params
		let val = @value.c

		if !params or params:as != 'declaration'
			return val

		if datatype and STACK.tsc
			let typ = C(datatype)
			val += '?' if datatype.isOptional
			# now check if type is optional
			val += ':' + typ

		# include type??
		if @defaults
			return "{val} = {@defaults.c}"
		elif option(:splat)
			return "..." + val
		else
			return val

	def visit stack
		@defaults.traverse if @defaults
		@value.traverse(declaring: 'param') if @value

		# self.variable ||= scope__.register(name,self)

		if @value isa Identifier
			@value.@variable ||= scope__.register(@value.symbol,@value,type: type)
		self

	def assignment
		OP('=',variable.accessor,defaults)

	def isExpressable
		!defaults || defaults.isExpressable

	def dump
		{loc: loc}

	def loc
		@name && @name.region

	def toJSON
		{
			type: typeName
			name: name
			defaults: defaults
		}

export class RestParam < Param

export class BlockParam < Param

	def c
		"blockparam"

	def loc
		# hacky.. cannot know for sure that this is right?
		var r = name.region
		[r[0] - 1,r[1]]

export class OptionalParam < Param

export class NamedParam < Param

export class RequiredParam < Param

export class ParamList < ListNode

	prop splat
	prop block

	def at index, force = no, name = null, value = null
		if force
			while index >= count
				let curr = count == index
				let val = curr ? value : null
				add(Param.new(curr && name || "_{count}",val))
				# is this ever traversed?

			# need to visit at the same time, no?
		list[index]

	def metadata
		filter(|par| !(par isa Meta))

	def toJSON
		metadata

	def jsdoc
		let out = []
		for item in nodes when item isa Param
			if item.datatype # option(:datatype)
				out.push(item.jsdoc())
		let doc = out.join('\n')
		return (doc ? '/**\n' + doc + '\n*/\n' : '')

	def visit
		var blk = filter(|par| par isa BlockParam)

		if blk:length > 1
			blk[1].warn "a method can only have one &block parameter"

		elif blk[0] && blk[0] != last
			blk[0].warn "&block must be the last parameter of a method"
			# warn "&block must be the last parameter of a method", blk[0]

		# add more warnings later(!)
		# should probably throw error as well to stop compilation

		# need to register the required-pars as variables
		super

	def js stack
		return EMPTY if count == 0

		# FIXME This can be removed for v2?
		if stack.parent isa Block
			return head(stack)

		# items = map(|arg| arg.name.c ).compact
		# return null unless items[0]

		if stack.parent isa Code
			# return "params_here"
			# remove the splat, for sure.. need to handle the other items as well
			# this is messy with references to argvars etc etc. Fix
			let inline = !(stack.parent isa MethodDeclaration)
			var pars = nodes
			var opts = {as: 'declaration', typed: inline}
			# pars = filter(|arg| arg != @splat && !(arg isa BlockParam)) if @splat
			# pars = filter(|arg| arg isa RequiredParam or arg isa OptionalParam) if @splat
			AST.compact(nodes.map(|param|
				let part = param.c(opts)
				# let typ = STACK.tsc and inline and param.datatype
				# part = part + ' as ' + typ.c if typ
				return part
			)).join(",")
		else
			throw "not implemented paramlist js"

	def head o
		var reg = []
		var opt = []
		var blk = null
		var splat = null
		var named = null
		var arys = []
		var signature = []
		var idx = 0

		nodes.forEach do |par,i|
			if par isa RawScript
				return

			par.index = idx
			if par isa OptionalParam
				signature.push('opt')
				opt.push(par)
			elif par isa BlockParam
				signature.push('blk')
				blk = par
			else
				signature.push('reg')
				reg.push(par)
			idx++

		if named
			var namedvar = named.variable

		# var opt = nodes.filter(|n| n isa OptionalParam)
		# var blk = nodes.filter(|n| n isa BlockParam)[0]
		# var splat = nodes.filter(|n| n isa SplatParam)[0]

		# simple situation where we simply switch
		# can probably optimize by not looking at arguments at all
		var ast = []
		var isFunc = do |js| "typeof {js} == 'function'"

		# This is broken when dealing with iframes anc XSS scripting
		# but for now it is the best test for actual arguments
		# can also do constructor.name == 'Object'
		var isObj = do |js| "{js}.constructor === Object"
		var isntObj = do |js| "{js}.constructor !== Object"
		# should handle some common cases in a cleaner (less verbose) manner
		# does this work with default params after optional ones? Is that even worth anything?
		# this only works in one direction now, unlike TupleAssign

		# we dont really check the length etc now -- so it is buggy for lots of arguments

		# if we have optional params in the regular order etc we can go the easy route
		# slightly hacky now. Should refactor all of these to use the signature?
		if !named && !splat && !blk && opt:length > 0 && signature.join(" ").match(/opt$/)
			for par,i in opt
				ast.push "if({par.name.c} === undefined) {par.name.c} = {par.defaults.c}"

		elif named && !splat && !blk && opt:length == 0 # and no block?!
			# different shorthands
			# if named
			ast.push "if(!{namedvar.c}||{isntObj(namedvar.c)}) {namedvar.c} = \{\}"

		elif blk && opt:length == 1 && !splat && !named
			var op = opt[0]
			var opn = op.name.c
			var bn = blk.name.c
			ast.push "if({bn}==undefined && {isFunc(opn)}) {bn} = {opn},{opn} = {op.defaults.c}"
			ast.push "if({opn}==undefined) {opn} = {op.defaults.c}"

		elif blk && named && opt:length == 0 && !splat
			var bn = blk.name.c
			ast.push "if({bn}==undefined && {isFunc(namedvar.c)}) {bn} = {namedvar.c},{namedvar.c} = \{\}"
			ast.push "else if(!{namedvar.c}||{isntObj(namedvar.c)}) {namedvar.c} = \{\}"

		elif opt:length > 0 || splat # && blk  # && !splat

			var argvar = scope__.temporary(self, pool: 'arguments').predeclared.c
			var len = scope__.temporary(self, pool: 'counter').predeclared.c

			var last = "{argvar}[{len}-1]"
			var pop = "{argvar}[--{len}]"
			ast.push "var {argvar} = arguments, {len} = {argvar}.length"

			if blk
				var bn = blk.name.c
				if splat
					ast.push "var {bn} = {isFunc(last)} ? {pop} : null"
				elif reg:length > 0
					# ast.push "// several regs really?"
					ast.push "var {bn} = {len} > {reg:length} && {isFunc(last)} ? {pop} : null"
				else
					ast.push "var {bn} = {isFunc(last)} ? {pop} : null"

			# if we have named params - look for them before splat
			# should probably loop through pars in the same order they were added
			# should it be prioritized above optional objects??
			if named
				# should not include it when there is a splat?
				ast.push "var {namedvar.c} = {last}&&{isObj(last)} ? {pop} : \{\}"

			for par,i in opt
				ast.push "if({len} < {par.index + 1}) {par.name.c} = {par.defaults.c}"

			# add the splat
			if splat
				var sn = splat.name.c
				var si = splat.index

				if si == 0
					ast.push "var {sn} = new Array({len}>{si} ? {len} : 0)"
					ast.push "while({len}>{si}) {sn}[{len}-1] = {pop}"
				else
					ast.push "var {sn} = new Array({len}>{si} ? {len}-{si} : 0)"
					ast.push "while({len}>{si}) {sn}[--{len} - {si}] = {argvar}[{len}]"

			# if named
			# 	for k,i in named.nodes
			# 		# OP('.',namedvar) <- this is the right way, with invalid names etc
			# 		var op = OP('.',namedvar,k.key).c
			# 		ast.push "var {k.key.c} = {op} !== undefined ? {op} : {k.value.c}"

			# if named

			# return ast.join(";\n") + ";"
			# return "if({opt[0].name.c} instanceof Function) {blk.c} = {opt[0].c};"

		elif opt:length > 0
			for par,i in opt
				ast.push "if({par.name.c} === undefined) {par.name.c} = {par.defaults.c}"

		# now set stuff if named params(!)

		if named
			for k,i in named.nodes
				# console.log "named var {k.c}"
				var op = OP('.',namedvar,k.c).c
				ast.push "var {k.c} = {op} !== undefined ? {op} : {k.defaults.c}"

		if arys:length
			for v,i in arys
				# create tuples
				v.head(o,ast,self)
				# ast.push v.c

		# if opt:length == 0
		return ast:length > 0 ? (ast.join(";\n") + ";") : EMPTY

# Legacy. Should move away from this?
export class ScopeVariables < ListNode

	# for later, moz-ast style
	prop kind
	prop split

	# we want to register these variables in
	def add name, init, pos = -1
		var vardec = VariableDeclarator.new(name,init)
		vardec.variable = name if name isa Variable
		pos == 0 ? unshift(vardec) : push(vardec)
		vardec

	def load list

		list.map do |par| VariableDeclarator.new(par.name,par.defaults,par.splat)

	def isExpressable
		nodes.every(|item| item.isExpressable)

	def js o
		return EMPTY if count == 0

		# When is this needed?
		if count == 1 && !isExpressable
			first.variable.autodeclare
			return first.assignment.c

		var keyword = 'var'
		var groups = {}

		nodes.forEach do |item|
			let variable = item.@variable or item

			let typ = variable isa Variable and variable.type
			if typ
				groups[typ] ||= []
				groups[typ].push(item)
			# elif item.@variable
			#	console.log 'variable without type'
			# else
			#	console.log "no variable type?? {item:constructor} {item.type}"

		if groups['let'] and (groups['var'] or groups['const'])
			groups['let'].forEach do |item|
				(item.@variable or item).@virtual = yes

		elif groups['let']
			keyword = 'let'

		# FIX PERFORMANCE
		# This is used in let scope as well - inflexible
		# Is this used by
		if split and true
			let out2 = []
			for own k,v of groups
				out2.push "{k} {AST.cary(v,as: 'declaration').join(', ')};"
			return out2.join("\n")

		var out = AST.compact(AST.cary(nodes,as: 'declaration')).join(", ")
		out ? "{keyword} {out}" : ""

export class VariableDeclarator < Param

	prop type
	# can possibly create the variable immediately but wait with scope-declaring
	# What if this is merely the declaration of a system/temporary variable?
	def visit
		# even if we should traverse the defaults as if this variable does not exist
		# we need to preregister it and then activate it later
		self.variable ||= scope__.register(name,null, type: @type or 'var')
		defaults.traverse if defaults
		# WARN what if it is already declared?
		self.variable.declarator = self
		self.variable.addReference(name)
		self

	# needs to be linked up to the actual scoped variables, no?
	def js o
		return null if variable.@proxy

		var defs = defaults
		let typ = STACK.tsc and variable.datatype

		# console.log 'compile variable!!'
		# FIXME need to deal with var-defines within other statements etc
		# FIXME need better syntax for this
		if defs != null && defs != undefined
			defs = defs.c(expression: yes) if defs isa Node
			if typ
				# ever used?
				defs = "{typ.c}({defs})"

			"{variable.c} = {defs}"
		elif typ
			# "{variable.c} = {typ.c}(undefined)"
			"{variable.c}:{typ.c}"
		else
			"{variable.c}"

	def accessor
		self

export class VarDeclaration < Node

	prop kind
	prop left
	prop right

	def op
		@op

	def type
		@kind

	def initialize left, right, kind, op = '='
		@op = op
		@left = left
		@right = right
		@kind = kind

	def visit stack
		# WARN not always correct
		unless @left isa Identifier and @right isa Func
			@right.traverse if @right

		@variables = scope__.captureVariableDeclarations do
			@left.traverse(declaring: type) if @left

			# replace directly
			# should the left side be wrapped in a VarDeclLeft node?
			if @left isa Identifier
				# TODO add an identifier.declare method for this
				@left.@variable ||= scope__.register(@left.symbol,@left,type: type)

		@right.traverse if @right

		# elif @left isa Obj
		# 	# need to traverse the object to register variables
		# 	# if it is a let we might also need to rename them though

		self

	def isExpressable
		no

	def consume node
		if node isa TagLike
			return self

		if node isa PushAssign or node isa Return
			let ast = self
			if right and !right.isExpressable
				let temp = scope__.temporary(self)
				let ast = right.consume(OP('=',temp,NULL))
				right = temp
				return Block.new([ast,BR,self.consume(node)])

			return Block.new([ast,BR,@left.consume(node)])

		if node isa Return
			return Block.new([self,BR,@left.consume(node)])

		super(node)

	def c o
		if right and !right.isExpressable
			let temp = scope__.temporary(self)
			let ast = right.consume(OP('=',temp,NULL))
			right = temp
			return Block.new([ast,BR,self]).c(o)
		# testing this
		return super(o)

	def js
		let out = ''
		let kind = kind
		let typ = (datatype or (@left and @left.datatype))
		if STACK.tsc and @variables:length > 1 and @variables.some(do $1.vartype)
			kind = 'let' # or var?
			for item in @variables
				let itemjs = item.c()
				if item.vartype
					# Rename to datatype?
					
					# out += item.vartype.c + ' '
					itemjs += ':' + item.vartype.c + ' '

				out += "{M(kind,keyword)} {itemjs};\n"
			out += "({left.c()}"
			if right
				out += " = {right.c(expression: true)}"
			out += ")"
		else
			out += "{M(kind,keyword)} {left.c()}"

			if right
				
				out += " = {right.c(expression: true)}"

		if option(:export)
			out = M('export',option(:export)) + " {out}"

		if typ
			yes
			# out = typ.c() + '\n' + out

		return out

# TODO clean up and refactor all the different representations of vars
export class VarName < ValueNode

	prop variable
	prop splat

	def initialize a,b
		super
		@splat = b

	def visit
		# should we not lookup instead?
		# FIXME p "register value {value.c}"
		self.variable ||= scope__.register(value.c,null)
		self.variable.declarator = self
		self.variable.addReference(value)
		self

	def js o
		variable.c

	def c
		variable.c

# CODE

export class Code < Node

	prop head
	prop body
	prop scope
	prop params

	def isStatementLike
		yes

	def scopetype
		Scope

	def visit
		@scope.visit if @scope
		# @scope.parent = STACK.scope(1) if @scope
		self

export class CodeBlock < Code
	def initialize body, opts
		@traversed = no
		@body = AST.blk(body)
		@scope = FlowScope.new(self)
		@body.head = @scope.head
		@options = {}

	def visit
		@scope.visit
		@body.traverse

		self

	def c
		# add curly braces?
		@body.c

# Rename to Program?
export class Root < Code

	def initialize body, opts
		@traversed = no
		@body = AST.blk(body)
		@scope = RootScope.new(self,null)
		@options = {}

	def loc
		@body.loc

	def visit
		ROOT = STACK.ROOT = @scope
		try
			scope.visit
			body.traverse
			if body.first isa Terminator
				body.first.@first = yes
		catch e
			let err = ImbaTraverseError.wrap(e)
			err.@sourcePath = OPTS:sourcePath
			err.@loc = STACK.currentRegion()
			throw err

	def compile o, script = {}
		STACK.reset # -- nested compilation does not work now
		@scope.options = OPTS = STACK.@options = @options = o or {}
		STACK.SOURCECODE = script:sourceCode
		ROOT = STACK.root = @scope
		@scope.@imba.configure(o)
		traverse

		STACK.root = @scope

		

		if o:bundle
			if o:cwd and STACK.isNode
				let abs = fspath.resolve(o:cwd,o:sourcePath)
				let rel = fspath.relative(o:cwd,abs).split(fspath:sep).join('/')

				let np = @scope.importProxy('path').proxy
				# TODO Test this thoroughly - better to replace fater the fact?
				@scope.lookup('__filename'):c = do LIT("{np:resolve}({STR(rel).c})").c
				@scope.lookup('__dirname'):c = do LIT("{np:dirname}({np:resolve}({STR(rel).c}))").c
			else
				@scope.lookup('__filename').@c = STR(o:sourcePath).c
				@scope.lookup('__dirname').@c = STR(fspath.dirname(o:sourcePath)).c

		if o:onTraversed isa Function
			o:onTraversed(self,STACK)

		let sheet = STACK.css
		let css = sheet.toString

		if sheet:transitions
			runtime:transitions

		if css and (!o:styles or o:styles == 'inline')
			runtime:styles

		var out = c(o)

		if STACK.tsc
			# out = "import 'imba/index.d.ts';\n{out}"
			out = "export \{\};String();import * as imba from 'imba';\n{out}\n"

			if (script:sourceCode and script:sourceCode.match(/(^|[\r\n])\# @nocheck[\n\r]/)) or o:nocheck
				out = "// @ts-nocheck\n{out}"

		script:rawResult = {
			js: out
			css: css
		}

		script:js = out
		script:css = css or ""
		script:sourceId = sourceId
		script:assets = scope.assets
		script:universal = STACK.meta:universal !== no

		if !STACK.tsc
			if script:css and (!o:styles or o:styles == 'inline')

				# let style = '`\n' + script:css + '\n`'
				# - we have to escape it - right?
				let style = JSON.stringify(script:css)

				script:js = "{script:js}\n{runtime:styles}.register('{script:sourceId}',{style});"
				if o:debug or true
					script:js += '\n/*\n' + script:css + '\n*/\n' # this we should only include when debugging

		if o:sourcemap or STACK.tsc
			let map = SourceMap.new(script,o)

			# sourcemap is not needed in tsc - only need to generate the ranges
			if STACK.tsc
				map.parse()
			else
				map.generate()
				script:sourcemap = map.result

			if o:sourcemap == 'inline'
				script:js += map.inlined

		unless o:raw
			script:css &&= SourceMapper.strip(script:css)
			script:js = SourceMapper.strip(script:js)

			if STACK.tsc
				# now combine comments - keep whitespace to not mess up sourcemapping
				script:js = script:js.replace(/\*\/\s[\r\n]+(\t*)\/\*\*/gm) do |m| m.replace(/[^\n\t]/g,' ')

		return script

	def js o
		var out = scope.c

		# find and replace shebangs
		var shebangs = []
		out = out.replace(/^[ \t]*\/\/(\!.+)$/mg) do |m,shebang|
			shebang = shebang.replace(/\bimba\b/g,'node')
			shebangs.push("#{shebang}\n")
			return ""

		out = shebangs.join('') + out

		return out

	def analyze o = {}
		# loglevel: 0, entities: no, scopes: yes
		STACK.loglevel = o:loglevel or 0
		STACK.@analyzing = true
		ROOT = STACK.ROOT = @scope
		OPTS = STACK.@options = {
			platform: o:platform
			loglevel: o:loglevel or 0
			analysis: {
				entities: (o:entities or no),
				scopes: (o:scopes ?= yes)
			}
		}

		traverse
		STACK.@analyzing = false

		return scope.dump

	def inspect
		true

export class ClassDeclaration < Code

	prop name
	prop superclass
	prop initor

	def consume node
		if node isa Return
			option('return',node)
			return self
		super

	def namepath
		@namepath ||= "{name ? name.c : '--'}"

	def metadata
		{
			type: 'class'
			namepath: namepath
			inherits: superclass?.namepath
			path: name and name.c.toString
			desc: @desc
			loc: loc
			symbols: @scope.entities
		}

	def loc
		if let d = option(:keyword)
			[d.@loc,body.loc[1]]
		else
			super

	def startLoc
		@startLoc ?= MSTART(option(:export),option(:keyword))

	def endLoc
		@endLoc ?= MEND(body)

	def toJSON
		metadata

	def isStruct
		keyword and String(keyword) == 'struct'

	def isMixin
		keyword and String(keyword) == 'mixin'
	
	def isInterface
		keyword and String(keyword) == 'interface'

	def hasMixins
		@mixins and @mixins:length > 0

	def isExtension
		option(:extension)

	def isExported
		option(:export)

	def isGlobal
		option(:global)

	def isStrict
		option(:strict)
	
	def isLoose
		!isStrict

	def tsId
		@tsId ||= '$$' + STACK.sourceId + 'C' + STACK.incr('cls') + '$$'	

	def isNamespaced
		@name isa Access

	def exportForDts
		return false

	def initialize name, superclass, body
		# what about the namespace?
		@traversed = no
		if name isa VarOrAccess
			name.@value.@options = name.@options
			name = name.@value
			

		@name = name # or LIT('')
		@superclass = superclass
		@scope = isTag ? TagScope.new(self) : ClassScope.new(self)
		@body = AST.blk(body) or ClassBody.new([])
		@entities = {} # items should register the entities as they come
		self

	def isTag
		no

	def staticInit
		# @superclass ? 'super.inherited instanceof Function && super.inherited(this)' : 'this'
		@staticInit ||= addMethod(initKey,[],'this').set(static: yes)
		# add static block

	def initKey
		@initKey ||= (STACK.tsc ? STACK.root.symbolRef('#__init__') : STACK.imbaSymbol('__init__')) # SymbolIdentifier.new('#__init__')

	def patchKey
		@patchKey ||= (STACK.tsc ? STACK.root.symbolRef('#__patch__') : STACK.imbaSymbol('__patch__')) # SymbolIdentifier.new('#__patch__')

	def refSym
		@refSym ||= STACK.getSymbol()

	def initPath
		@initPath ||= OP('.',LIT('super'),initKey)

	def virtualSuper
		@virtualSuper ||= @scope.parent.declare(:tmp,null,system: yes, type: 'let')

	def classReference
		@name

	def instanceInit
		return @instanceInit if @instanceInit
		let call = Super.callOp(initKey)
		if @superclass or @mixins:length
			call = OP('&&',LIT('deep'),OP('&&',OP('.',LIT('super'),initKey),call))
		let fn = addMethod(initKey,[],(isTag or @superclass) ? [call,BR] : '',{}) do |fun|
			yes
		fn.set(noreturn: yes)
		fn.params.at(0,yes,'$$',LIT('null'))
		fn.params.at(1,yes,'deep',LIT('true'))
		fn.params.at(2,yes,'fields',LIT('true'))
		@instanceInit = fn

	def instancePatch
		return @instancePatch if @instancePatch

		let body = []
		let fn = addMethod(patchKey,[],body,{}) do |fun|
			yes

		let param = fn.@params.at(0,yes,'$$',LIT('{}'))
		let fieldparam = fn.@params.at(1,yes,'fields',LIT('true'))

		if @superclass
			let call = Super.callOp(patchKey,[param,fieldparam])
			call = OP('&&',OP('.',LIT('super'),patchKey),call)
			fn.inject(call)

		fn.set(noreturn: yes)
		@instancePatch = fn

	def isInitingFields
		@inits or (@supernode and @supernode:isInitingFields and @supernode.isInitingFields)

	def visit
		let upscope = STACK.scope(1)
		let up = STACK.up

		
		if !STACK.tsc
			let refvar = SystemVariable.new(upscope,null,null,{type: 'let', ns: 'c', safe: yes})
			STACK.prependInBlock(Templated.new('let @name = Symbol()',name: refvar))
			@refSym = refvar

		else
			unless upscope isa RootScope
				set(notInRoot: yes)

		@body.@delimiter = ''
		let blk = STACK.up(Block)
		@decorators = blk and blk.collectDecorators

		STACK.pop(self)
		let sup = @superclass

		@path = @name
		@ownName = @name
		@realName = @name isa Access ? @name.right : @name

		if sup
			sup.traverse()
			# also visit and possibly declare the class name?
			if sup isa VarOrAccess
				if sup.@variable
					let val = sup.@variable.value
					if val isa ClassDeclaration
						@supernode = val
				elif sup.symbol == 'Object'
					set(implicitAny: yes)
					sup = @superclass = null

		if isExtension and @name
			@name.traverse()

			if @name isa Identifier
				@name.resolveVariable()

			if !isTag
				let extname
				@className = @name
				@ownName = STACK.toInternalClassName(@name)
				@mixinName = scope__.register(@ownName,null)
			else
				@className = LIT(@name.toClassName)
				@ownName = STACK.toInternalClassName(@name)
				@mixinName = scope__.register(@ownName,null)

		elif @name isa Identifier
			if !isTag or @name.isCapitalized
				@name.registerVariable('const')
				@name.@variable.value = self

		elif @name and !(@name isa Access)
			@name.traverse(declaring: self)
		elif @name
			@name.traverse()
		
		if STACK.tsc
			if (isGlobal or isExtension)
				@ownName = STACK.toInternalClassName(@name)

			# hmm
			elif isGlobal and !isExtension and !isNamespaced and option(:export)
				@exportName = STACK.toInternalClassName(@name)

		STACK.push(self)
		ROOT.entities.add(namepath,self)
		scope.visit
		set(iife: STACK.up isa Instantiation)

		var separateInitChain = true
		var fields = []
		var mixins = @mixins = []
		var signature = []
		var params = []
		var declaredFields = {}
		var restIndex = undefined
		var instanceMethodMap = {}

		@instanceMethodMap = instanceMethodMap
		@declaredFields = declaredFields

		for node in body
			if node isa ClassRelation
				mixins.push(node.value)


			if node isa ClassField
				if !node.isStatic
					let name = String(node.name)
					declaredFields[name] = node
					if separateInitChain
						node.set(restIndex: 0)
					# if node.watchBody
					#	console.log 'has watcher??'
			if node isa MethodDeclaration
				let name = node.rawName
				if node.isMember
					instanceMethodMap[name] = node

		# TODO No longer used - remove
		if option(:params)
			# find the rest param
			let add = []
			for param,index in option(:params)

				if param isa RestParam
					restIndex = index
					continue

				let name = String(param.name)
				let field = declaredFields[name]
				let dtyp = param.option(:datatype)

				if !field
					field = fields[name] = ClassField.new(param.name).set(
						datatype: dtyp
						value: param.defaults
					)
					# field.set(param: param)
					add.push(field)
					params.push(param)
				else
					if dtyp and !field.datatype
						field.set(datatype: dtyp)
					if param.defaults and !field.value
						field.set(value: param.defaults)

				if field
					field.set(paramIndex: index, paramName: name)

			for item in add.reverse
				body.unshift(item)
		
		# See if we called super anywhere
		body.traverse
		# console.log "Called?",option(:calledSuper)
		var ctor = body.option(:ctor)

		let tsc = STACK.tsc
		var inits = InstanceInitBlock.new()
		var staticInits = @staticInits = ClassInitBlock.new()
		var patches = InstancePatchBlock.new()

		if @mixins[0] and !tsc and !isExtension
			inits.add LIT("this[{refSym}]?.(...arguments)")

		var ctor = body.option(:ctor)

		let fieldNodes = body.filter do |node| node isa ClassField
		let allDecorators = []

		for node in fieldNodes
			if node.isExcluded
				continue

			if node.watchBody
				addMethod(node.watcherSymbol,[],[node.watchBody],{}) do |fn|
					node.@watchMethod = fn
					node.@watchParam = fn.params.at(0,yes,'e')

			if node.hasStaticInits and !node.option(:declareOnly)
				staticInits.add(node)

			if node.hasConstructorInits
				if isExtension
					if node.value
						node.@name.warn "field with value not supported in class extension"
				elif !node.option(:declareOnly)
					if !node.option(:wrapper) or node isa ClassProperty
						inits.add(node)
					patches.add(node)

			if !node.isStatic and restIndex != null
				node.set(restIndex: restIndex)

		if !tsc and @decorators
			let op = util.decorate(Arr.new(@decorators),THIS)
			staticInits.add([op,BR])
			allDecorators.push(@decorators)

		for node,i in body
			if node.@decorators and !node.isExcluded

				let target = node.option(:static) ? THIS : PROTO
				let desc = LIT('null')
				let op = util.decorate(Arr.new(node.@decorators),target,node.name,desc)
				allDecorators.push(node.@decorators)
				staticInits.add([op,BR])

		let supers = sup or @mixins[0]

		if !inits.isEmpty and !tsc
			@inits = inits
			instanceInit
			inits.set(ctor: instanceInit)
			instanceInit.inject(inits)

			if isTag
				# tags always call init from outside the actual construction
				yes

			elif !@superclass
				let initop = OP('.',THIS,initKey)
				if !ctor
					ctor = addMethod('constructor',[],[],{})
					let param = ctor.params.at(0,yes,'$$',LIT('null'))
					let callop = CALL(initop,[param])
					unless tsc
						ctor.body.add([callop,BR],0)

				else
					let supr = ctor.option(:supr)
					if supr
						supr:real.set(target: initop,args: [])
					else
						ctor.body.add([CALL(initop,[]),BR],0)

			elif !@supernode or !@supernode.isInitingFields
				# we don't have an explicit constructor
				# if we cannot know on compiletime that the superclass
				# has an initor - we do need to call it here
				let op = OP('||',initPath,CALL(OP('.',THIS,initKey),[]))
				if !ctor
					ctor = addMethod('constructor',[],[Super.new(),BR,op],{})
				else
					let after = ctor.option(:injectInitAfter)
					ctor.inject(op,after ? {after: after} : 0)
				yes

		# Not supporting patches for now
		if !patches.isEmpty and !tsc and false
			instancePatch
			patches.set(ctor: instancePatch)
			instancePatch.inject(patches)

		if tsc and ctor and @autosuper
			ctor.body.add([LIT("super()"),BR],0)
		
		let cflags = 0

		if self isa ExtendDeclaration
			cflags = cflags | ClassFlags.IsObjectExtension

		if isMixin
			cflags = cflags | ClassFlags.IsMixin

		if isTag
			cflags = cflags | ClassFlags.IsTag

		if isExtension
			cflags = cflags | ClassFlags.IsExtension

		if @mixins:length
			cflags = cflags | ClassFlags.HasMixins

		if !tsc
			
			let hasInitedHook = !!instanceMethodMap["#__inited__"]
			let hasDecorators = allDecorators:length > 0

			if hasDecorators
				cflags = cflags | ClassFlags.HasDecorators
				STACK.use('hooks')
				let decosym = STACK.imbaSymbol('__hooks__')
				staticInits.unshift LIT("this.prototype[{decosym}] = {runtime:hooks}"), yes
			
			if !isTag and !ctor and (hasInitedHook or hasDecorators) # or anything else
				let ops = sup ? [Super.new(),BR] : [BR]
				ctor = addMethod('constructor',[],ops,{})

			if ctor and !isTag and !STACK.isStdLib
				ctor.inject CALL(STACK.corelib['inited$'],[THIS,refSym])

			if ctor
				cflags = cflags | ClassFlags.HasConstructor
				
			if option(:calledSuper)
				cflags = cflags | ClassFlags.HasSuperCalls

			let pars = [THIS,refSym,(@realName ? @realName.toStr : NULL),LIT(String(cflags))]
			if isExtension
				let clsname
				if isTag
					let namevar = @name.@variable
					clsname = namevar or CALL(runtime:getTagType,[name,STR(name.toClassName)])
					# if className == 'ImbaElement' or className == 'imba.Component'
					#	cls = runtime:Component
					pars.push(clsname)
				else
					pars.push(clsname = @className)

				if @mixins:length
					for mix in @mixins
						staticInits.add CALL(STACK.corelib['augment$'],[clsname,mix])
					@mixins:length = 0
			
			staticInits.add CALL(STACK.corelib['register$'],pars)

		if !staticInits.isEmpty and !tsc
			body.add([BR,staticInits])
		self

	def addMethod name, params, mbody, options, cb
		mbody = [LIT(mbody)] if mbody isa String
		name = Identifier.new(name) if name isa String
		let func = MethodDeclaration.new(params,mbody or [],name,null,options or {})
		self.body.unshift(func,yes)
		if cb isa Function
			cb(func)
		func.traverse
		return func

	def js o
		scope.virtualize # is this always needed?
		scope.context.value = name
		scope.context.reference = name

		var tsc = STACK.tsc
		var up = STACK.up
		var o = @options or {}

		var cname = @ownName isa Access ? @ownName.right : @ownName
		var origName = @name isa Access ? @name.right : @name

		var initor = null
		var sup = superclass

		if typeof cname != 'string' and cname
			cname = cname.c(mark: yes)

		@cname = cname

		var externalAccess = LIT(cname)

		let jsbody = body.c()
		let jshead = M('class',keyword)

		if name
			jshead += " {M cname, name}"
		else
			if up isa VarReference
				try jshead += " {up.@value.@symbol}"

		if tsc
			let up = STACK.parent
			let tpl = {
				body: jsbody
				name: @cname
				localName: @cname
				declareName: origName
				default: option(:default)
				abstract: option(:abstract) or isMixin or isInterface
				mixins: AST.cary(@mixins).join(', ') or null
				generics: @name and @name.option('generics')
				supr: superclass
				oid: tsId
				iife: (up isa Instantiation or option(:notInRoot))
				"return": option(:return)
				unsafe: option(:implicitAny)
				exportName: option(:default) or origName
			}

			if tpl:generics
				tpl:suprGenerics = tpl:generics.asGenericNames
				tpl:mapped =  no
			elif !tpl:abstract
				tpl:mapped = yes

			if (/^[\t\n]+$/).test(jsbody)
				tpl:body = null

			let str

			try tpl:ref = @name.variable.@value
			try tpl:export = option(:export) or !!@name.variable.@value.isExported
			try tpl:global = option(:global) or (@name and !@name.variable) or !!@name.variable.@value.isGlobal
			try tpl:path = isExtension and @name.variable.importPath

			# only if the target is originally global
			str = if isInterface
				if isGlobal
					'''
					namespace Global {
					export interface @declareName@generics @{extends @supr }{
						@{@unsafe? [index:string]: any; }
						@body
					}
					@{@mixins? export interface @declareName@generics @{extends @mixins }{ }}
					}

					@{@export? export import @declareName = Global.@declareName; }

					declare global {
						namespace globalThis {
							export import @declareName = Global.@declareName;
						}
					}
					'''
				else
					'''
					interface @declareName@generics @{extends @supr }{@{@unsafe? 
						[index:string]: any;}@body
					}
					'''
			
			elif !@name or tpl:iife
				'''
				class @name@generics @{extends @supr }{@{@unsafe? 
					[index:string]: any;}@body
				}
				'''
			
			elif isExtension and tpl:path
				'''
					@declareName
					declare module @path {
						@{@body? interface @declareName@generics extends @localName@generics {} }
						@{@mixins? interface @declareName@generics @{extends %mixins }{}}
					}
					@{@body? class @localName@generics { @body } }
				'''
			elif isExtension and tpl:global
				'''
				declare global {
					@{@body? interface @declareName@generics extends @localName@generics {} }
					@{@mixins? interface @declareName@generics @{extends %mixins }{}}
				}
				@{@body? class @localName@generics { @body } }
				'''
			elif isExtension
				'''

					@export @default interface @declareName@generics extends @localName@generics {}
					class @localName@generics { %body }
					@{@mixins? interface @declareName@generics @{extends %mixins }{}}
				'''
			elif isGlobal and !STACK.option('noAnyTypes')
				'''
				namespace Global {
				declare const @declareName$: unique symbol;
				export class @declareName@generics @{extends @supr }{
					declare [ImbaUnionType]: ImbaSubclassUnion<{[@declareName$]:true}>;
					@{@unsafe? [index:string]: any; }
					[@declareName$]:true;
					@body
				}
				@{@mixins? export interface @declareName@generics @{extends @mixins }{ }}
				}

				@{@export? export import @declareName = Global.@declareName; }

				declare global {
					namespace globalThis {
						export import @declareName = Global.@declareName;
					}
					@{@mapped? interface GlobalClassMap {@declareName:@declareName}}
				}
				'''
			elif isGlobal
				'''
				namespace Global {
				export class @declareName@generics @{extends @supr }{
					@{@unsafe? [index:string]: any; }
					@body
				}
				@{@mixins? export interface @declareName@generics @{extends @mixins }{ }}
				}

				@{@export? export import @declareName = Global.@declareName; }

				declare global {
					namespace globalThis {
						export import @declareName = Global.@declareName;
					}
					// @{@mapped? interface GlobalClassMap {@declareName:@declareName}}
				}
				'''
			elif false
				'''
				@{@mixins? %export %default interface %localName@generics @{extends %mixins }{}}
				declare const @declareName$: unique symbol;
				%export %default class %localName@generics @{extends %supr }{
					declare [ImbaUnionType]: ImbaSubclassUnion<{[@declareName$]:true}>;
					@{@unsafe? [index:string]: any;}
					[@declareName$]:true;
					@body
				}
				@{@mapped? declare global {
					 interface GlobalClassMap {[@declareName$]:@declareName}
				}}
				'''
			else
				'''
				@{@mixins? %export %default interface %localName@generics @{extends %mixins }{}}
				%export %default class %localName@generics @{extends %supr }{
					@{@unsafe? [index:string]: any;}
					@body
				}
				'''

			return TPL(tpl,str)

		if @mixins:length
			jshead += " {mo('extends')} {CALL(STACK.corelib['multi$'],[refSym,sup or NULL].concat(@mixins)).c}"
		elif sup
			jshead += " {mo('extends')} {M(sup)}"

		if name isa Access and !exportForDts and !isExtension
			jshead = "{name.c} = {jshead}"

		if option(:export) # or (tsc and (exportForDts or option(:global)))
			# beware of double export
			if option(:default)
				jshead = "{mo('export')} {mo('default')} {jshead}"
			else
				jshead = "{mo('export')} {jshead}"

		let js = "{jshead} \{{jsbody}\}"

		if option(:global)
			let access = name isa Access
			let getter = name isa Access ? name.c : @cname
			js = "{js}; {scope__.root.globalRef}.{@cname} = {getter}"

		return js

export class ExtendDeclaration < ClassDeclaration

export class TagDeclaration < ClassDeclaration

	def isTag
		yes

	def isInitingFields
		yes

	def namepath
		"<{name}>"

	def metadata
		Object.assign(super,{
			type: 'tag'
		})

	def cssns
		@cssns ||= @scope.cssns

	def cssid
		@cssid ||= @scope.cssid

	def classReference
		LIT(@name.toClassName)

	def cssref scope
		if isNeverExtended and !superclass
			return @cssns

		if scope
			let s = scope.closure
			return s.memovar('_ns_',OP('||',OP('.',s.context,'_ns_'),STR('')))
		else
			OP('||',OP('.',THIS,'_ns_'),STR(''))

	def isNeverExtended
		if name and name.isClass
			return !option(:export) and !option(:extended)
		else
			no

	def visit
		if STACK.hmr
			self.cssid
			self.cssns

		super

		let sup = superclass

		if !name.isClass
			set(global: yes)

		@config = {}

		if sup and !STACK.tsc
			if (sup.isNative or sup.isNativeSVG)
				let op = sup.nativeCreateNode
				op = util.extendTag(op,THIS)
				addMethod('create$',[],[op]).set(static: yes)
				set(extends: Obj.wrap(extends: sup.name))
				@config:extends = sup.name

			elif sup.isClass
				sup.resolveVariable(scope__.parent)
				let up = sup.@variable && sup.@variable.value
				up.set(extended: self) if up

		if @elementReferences
			for own ref,child of @elementReferences
				if STACK.tsc
					let val = child.option(:reference)
					let typ = child.type
					let op = "{M(AST.sym(val),val)}"
					if typ and typ:toClassName
						op += " = new {typ.toClassName}"

					self.body.unshift(LIT(op + ';'),yes)

		# add some default css
		if !STACK.tsc and name and name:toNodeName and !option(:extension)
			# name.@nodeName ||= STACK.sourceId + '-' + STACK.generateId('')
			let name = name.toNodeName
			name = name + '-tag' if name.indexOf('-') == -1
			STACK.css.add name + ' { display:block; }'

		if option(:export) and name and name:isLowerCase and name.isLowerCase
			warn "Lowercased tags are globally available - not exportable", loc: option(:export)
		return

	def addElementReference name, child
		let refs = @elementReferences ||= {}

		if refs[name] and refs[name] != child
			child.warn("Duplicate elements with same reference", loc: name)
		else
			refs[name] = child
			child.set(tagdeclbody: @body)
		return child

	def js s
		scope.virtualize # is this always needed?
		scope.context.value = name
		scope.context.reference = name
		
		let tsc = STACK.tsc
		let className = name.toClassName
		let sup = superclass

		let anonGlobalTag = !option(:extension) and (!name.isClass) and tsc

		let tpl = {
			name: name.@str
			mixins: AST.cary(@mixins).join(', ') or null
		}

		if tsc
			tpl:declareName = tpl:localName = MappedString.new(className,name)

			if isGlobal or isExtension
				className = tpl:localName = InternalName.new(name)

		if sup and sup.@variable
			sup = sup.@variable
		elif sup
			sup = CALL(runtime:getSuperTagType,[sup,STR(sup.toClassName),runtime:Component])
		else
			sup = runtime:Component

		if tsc
			sup = superclass ? superclass.toClassName : LIT('imba.Component')

			if !isExtension
				# body.unshift(LIT("getTagName()\{ return '{tpl:name}' \}"),yes)
				# only if we do avoid the data thing
				# body.unshift(LIT("data = this.dataForTagName('{tpl:name}')"),yes)
				body.unshift(LIT('static $$TAG$$:true'),yes)
				body.unshift(LIT('constructor(){ super() }',@name),yes)

			tpl:body = body.c

			if !isExtension and !@declaredFields:data and !@instanceMethodMap:data
				tpl:body = "data = this.dataForTagName('{tpl:name}')\n" + tpl:body

			tpl:default = option(:default)
			tpl:superName = superclass ? MappedString.new(sup,superclass) : sup

			try tpl:export = option(:export) or !!@name.variable.@value.isExported

			if isExtension and isGlobal
				return TPL tpl, '''
					class %localName { %body }
					declare global { interface %declareName extends %localName {} }
				'''
			elif isExtension
				try tpl:path = @name.variable.importPath
				
				if tpl:path
					return TPL tpl, '''
						%declareName
						declare module %path { interface %declareName extends %localName {} }
						class %localName { %body }
					'''

				return TPL tpl,'''
					class %localName { %body }
					%export interface %declareName extends %localName {}
				'''
			elif isGlobal
				return TPL tpl,'''
					namespace Global {
					export class %declareName extends %superName { %body }
					@{@mixins? export interface %declareName extends @mixins { }}
					}
					declare global {
						namespace globalThis { export import %declareName = Global.%declareName }
						interface HTMLElementTagNameMap { "%name": %declareName }
					}
					
					
				'''
			else
				return TPL tpl,'''
				@{@mixins? %export %default interface %localName extends @mixins { }}
				%export %default class %localName extends %superName { %body }
				'''

		elif option(:extension)
			let namevar = @name.@variable
			let cls = namevar or CALL(runtime:getTagType,[name,STR(name.toClassName)])
			if className == 'ImbaElement' or className == 'imba.Component'
				cls = runtime:Component
			let tagname = TagTypeIdentifier.new(name)
			# @className = LIT('1232')

			return "({M('class',option(:keyword))} \{{body.c}\})"

		else
			if name.isNative
				name.error "tag {name.symbol} already exists"

		let closure = scope__.parent
		# console.log @cssns,@cssid
		@config:cssns = cssns if @cssns
		@config:cssid = cssid if @cssid
		@config:name = name.symbol if name.isClass
		tpl:config = Obj.wrap(@config)

		@staticInits.add([BR,CALL(runtime:defineTag,[name,THIS,tpl:config])])

		if @staticInit
			@staticInits.add([BR,CALL(OP('.',THIS,initKey),[])])
			
		let jsbody = body.c()
		
		let jshead = "{M 'class', keyword} {M className, name} {mo('extends')} "

		if @mixins:length
			jshead += "{CALL(STACK.corelib['multi$'],[refSym,sup or NULL].concat(@mixins)).c}"
		elif sup
			jshead += "{M(sup)}"

		if option(:export)
			if option(:default)
				jshead = "{mo 'export'} {mo 'default'} {jshead}"
			else
				jshead = "{mo 'export'} {jshead}"

		return "{jshead} \{{jsbody}\}"

export class Func < Code

	prop name
	prop params
	prop target
	prop options
	prop type
	prop context

	def scopetype do FunctionScope

	def initialize params, body, name, target, o
		@options = o
		var typ = scopetype
		@traversed = no
		@body = AST.blk(body)
		@scope ||= (o and o:scope) || typ.new(self)
		@scope.params = @params = ParamList.new(params)
		@name = name || ''
		@target = target
		@type = :function
		@variable = null
		self

	def inject line, o
		@body.add([line,BR],o)

	def nonlocals
		@scope.@nonlocals

	def isGlobal
		option(:global)

	def returnType
		datatype

	def visit stack, o
		# any function inside descriptors should compile as strong scopes
		if stack.@descriptor and !stack.tsc
			unless stack.indexOf(Func) > stack.indexOf(Descriptor)
				@scope = MethodScope.new(self) # (o and o:scope) || typ.new(self)
				@scope.params = @params # = ParamList.new([])

		scope.visit

		if @desc
			@desc.@skip = yes

		@context = scope.parent

		@params.traverse(declaring: 'arg')
		@body.traverse # so soon?

	def funcKeyword
		let str = "function"
		str = "async {str}" if option(:async)
		return str

	def jsdoc
		return ''

	def js s,o
		body.consume(ImplicitReturn.new) unless option(:noreturn)
		var ind = body.@indentation
		# var s = ind and ind.@open
		body.@indentation = null if ind and ind.isGenerated
		var code = scope.c(indent: (!ind or !ind.isGenerated), braces: yes)

		var name = typeof @name == 'string' ? @name : @name.c
		name = name ? ' ' + name.replace(/\./g,'_') : ''
		var keyword = o and o:keyword != undefined ? o:keyword : funcKeyword


		var out = "{M(keyword,option('def') or option('keyword'))}{helpers.toValidIdentifier(name)}({params.c}) "

		if STACK.tsc and returnType
			out += ':' + returnType.c

		
		out += code
		# out = "async {out}" if option(:async)
		out = "({out})()" if option(:eval)
		return out

	def shouldParenthesize par = up
		par isa Call && par.callee == self
		# if up as a call? Only if we are

export class IsolatedFunc < Func
	prop leaks

	def scopetype do IsolatedFunctionScope

	def isStatic
		yes

	def isPrimitive
		yes

	def visit stack
		super

		return if stack.tsc

		if let leaks = @scope.@leaks
			@leaks = []
			leaks.forEach do |shadow,source|
				if shadow.@name == 'self' and USE_SAFE_RENDER_SELF
					@useSelf = shadow
				else
					shadow.@proxy = @params.at(@params.count,yes)
					@leaks.push(source)
		self

export class IifeFunc < Func

	def js s,o
		body.consume(ImplicitReturn.new) unless option(:noreturn)
		var ind = body.@indentation
		var out = body.c(braces: yes)
		return "(()=>{out})()"

export class Walker
	prop leaks
	def initialize fn
		@func = fn
		@leaks = no
		@matches = []

	def test node
		no

export class Lambda < Func
	def scopetype
		var k = option(:keyword)
		(k and k.@value == 'ƒ') ? (MethodScope) : (LambdaScope)

export class AmperWalker < Walker
	prop deopt
	
	def test node
		if node isa Call
			node.@args.consume(self)
			node.@callee.consume(self)
			return

		if node isa VarOrAccess and node.@isSelf
			@deopt = yes

		let variable = node.@variable
		if variable and !variable.isGlobal
			@deopt = yes

		if node isa This or node isa Self
			@self = node

export class AmperFunc < Lambda

	def scopetype
		LambdaScope

	def js s,o
		# traverse into the function to see if it is dynamic
		let walker = AmperWalker.new(self)
		body.consume(walker)

		# hash the output? Or the source? Maybe the source is enough?
		var out = body.c(braces: no)

		if !walker.deopt and !STACK.tsc
			let raw = body.sourcecode
			let sym = raw.replace(/[\"]/g,"'")
			let that = walker.@self
			let pars = [LIT('"' + sym + '"'),walker.@self or LIT('globalThis'),LIT("(v$)=>{out}")]

			# moving scopeless amperfunctions out to the top of the file
			unless that
				let scop = scope__.root
				let lit = LIT("{runtime:memofunc}({pars.map(do $1.c).join(',')})")
				let ref = scop.@ampermap[sym]

				unless ref
					let name = 'ƒ' + STACK.incr('ƒ') # sym.replace('&.','ƒ')
					ref = scop.@ampermap[sym] = scop.declare(name,lit)

				return ref.c

			return "{runtime:memofunc}({pars.map(do $1.c).join(',')})"

		return "((v$)=>{out})"

export class RescueFunc < Func

	def visit o
		@scope = null
		body.traverse

		if option(:async)
			var fnscope = o.up(Func)
			fnscope && fnscope.set(async: yes)

		self

	def js s,o
		let out
		if STACK.tsc
			out = body.c(braces: no)
			return CALL(STACK.corelib['rescue$'],[LIT(out)]).c

		body = body.consume(ImplicitReturn.new)
		out = body.c(braces: no)

		let fn = '()=>{ try { ' + out + ' } catch(e) { return e; } }'

		if option('async')
			return "await (async {fn})()"
		else
			return "({fn})()"



export class ClosedFunc < Func
	def scopetype
		MethodScope

export class TagFragmentFunc < Func

	def scopetype
		# caching still needs to be local no matter what?
		option(:closed) ? (MethodScope) : (LambdaScope)

export class MethodDeclaration < Func

	prop variable
	prop decorators

	def scopetype do MethodScope

	def consume node
		if node isa Return
			option('return',yes)
			return self
		super

	def isExcluded
		if STACK.tsc
			return @envs and @envs.find(do $1.@key == 'JS') or false

		if @envs
			return !@envs.find(do $1.isTruthy)
		return false

	def identifier
		@name

	def rawName
		@name isa Identifier ? (@name.toRaw) : ""

	def metadata
		{
			type: "method"
			name: "" + name
			namepath: namepath
			params: @params.metadata
			desc: @desc
			scopenr: scope.@nr
			loc: loc
		}

	def loc
		if let d = option(:def)
			let end = body.option(:end) or body.loc[1]
			[d.@loc,end]
		else
			[0,0]

	def isGetter
		@type == 'get'

	def isSetter
		@type == 'set'

	def isConstructor
		String(name) == 'constructor'

	def isMember
		!option(:static)

	def toJSON
		metadata

	def namepath
		return @namepath if @namepath

		var name = String(name.c)
		var sep = (option('static') ? '.' : '#')
		if target
			let ctx = target
			# console.log "target?? {@target.@parent} {@context.node}"
			if ctx.namepath == "ValueNode"
				ctx = @context.node

			@namepath = ctx.namepath + sep + name
		else
			@namepath = '&' + name

	def visit
		@type = option(:type) or (option(:def)?.@value or 'def')
		@decorators = up?.collectDecorators

		return if isExcluded

		var o = @options
		scope.visit
		let scop = scope

		if isSetter and @params.len > 1
			if @params.len > 2
				console.warn "setter with more than two params not allowed",name

			let prev = @params.pop()
			# console.warn "setter with more than one param!!",name
			let op = OP('=',VarReference.new(prev.@value,'const'),OP('.',SELF,name))
			@body.add(op,0)

		if option(:inObject)
			@params.traverse
			@body.traverse
			return self

		var closure = @context = scope.parent.closure

		if closure isa RootScope and !target and !(@name isa DecoratorIdentifier)
			scope.@context = closure.context

		elif closure isa MethodScope and !target and !(@name isa DecoratorIdentifier)
			scope.@selfless = yes

		@params.traverse

		if @name:isPredicate and @name.isPredicate and !isSetter and !isGetter
			@name.warn "Only getters/setters should end with ?"

		if target isa Identifier
			if let variable = scope.lookup(target.toString)
				target = variable
			# should be changed to VarOrAccess?!

		if String(name) == 'initialize' and (closure isa ClassScope) and !(closure isa TagScope)
			self.type = :constructor

		if String(name) == 'constructor' or isConstructor
			up.set(ctor: self)
			set(noreturn: yes)

		# instance-method / member
		if closure isa ClassScope and !target
			@class = closure.node
			@target = closure.prototype
			let inExt = closure.node.option('extension')
			set(
				prototype: @target,
				inClassBody: yes,
				inExtension: inExt
			)

			closure.annotate(self)

		if target isa Self
			@target = closure.context
			closure.annotate(self)
			set(static: yes)

		elif o:variable

			@variable = scope.parent.register(name, self, type: String(o:variable))
			warn "{String(o:variable)} def cannot have a target" if target

		elif !target

			@variable = scope.parent.register(name,self,type: 'const')
			yes

		if o:export and !(closure isa RootScope)
			warn("cannot export non-root method", loc: o:export.loc)

		ROOT.entities.add(namepath,self)

		@body.traverse

		# traverse returnType
		returnType.traverse if returnType

		if isConstructor
			if !@class.@superclass and @class.hasMixins and !STACK.tsc
				scope.head.unshift(LIT('super()'))
			
			let ref = scope__.context.@reference
			let supr = option(:supr)
			let node = supr and supr:node
			let block = supr and supr:block

			if ref and node
				ref.declarator.@defaults = null
				let op = OP('=',ref,This.new)
				block.replace node, [node,op]

		self

	def supername
		type == :constructor ? type : name

	# FIXME export global etc are NOT valid for methods inside any other scope than
	# the outermost scope (root)

	def js stack,co = {}
		var o = @options
		var tsc = STACK.tsc

		if option(:declareOnly) and !tsc
			return ''

		if isExcluded
			return ''

		# need to have collected them already?
		# if isConstructor and !option(:supr) and @class and @class.@mixins:length and !tsc
		# 	# Must happen above the potential first reference to this
		# 	body.add(LIT('super(...arguments)'),0)

		# FIXME Do this in the grammar - remnants of old implementation
		unless type == :constructor or option(:noreturn) or isSetter()
			if option(:chainable)
				body.add(ImplicitReturn.new(scope.context))
			elif option(:greedy)
				# haaack
				body.consume(GreedyReturn.new)
			else
				body.consume(ImplicitReturn.new)

		var code = scope.c(indent: yes, braces: yes)
		var name = typeof @name == 'string' ? @name : @name.c(as: 'field')
		var star = option(:yield) ? '*' : ''
		var out = ""

		var tpl = tsc and {
			kind: option(:keyword)
			static: option(:static)
			export: option(:export)
			declare: option(:declareOnly)
			default: option(:default)
			protected: option(:protected)
			async: option(:async)
			code: code # with braces
			key: M(name, null, as: 'field') # should not be precompiled
			name: @name
			get: isGetter ? option(:keyword) : null
			set: isSetter ? option(:keyword) : null
			returnType: returnType
			yield: option(:yield) && LIT('*')
			params: params
			generics: @name and @name.option('generics')
			function: !option(:inClassBody) and !option(:inObject)
			decorators: @decorators
		}

		if tsc
			if (@class and @class.isInterface)
				tpl:code = ';'
			elif tpl:declare
				tpl:code = '{ return }' if SourceMapper.strip(tpl:code).replace(/[\s\n\t]/g,'') == '{}'
				

			let generics = {}
			let ret = returnType
			let paramtypes = params.map do $1.datatype

			let all = [ret].concat(paramtypes.reverse)

			for param,i in params
				let typ = param.datatype
				if typ
					typ.visit
					if typ.@genericWrap
						let key = '$' + (i + 1)
						generics[key] = typ.makeGeneric(key)

			for typ in all
				continue unless typ isa TypeAnnotation
				typ.visit

				if typ.@generics

					typ.@generics.forEach do
						let key = $1
						let param = $1.match(/^\$\d+$/) ? params.at(parseInt($1.slice(1)) - 1) : null
						# dont push multiple times
						try
							if !param.datatype
								param.datatype = TypeAnnotation.new(key,LIT(''))
							# if there is no data-type?
							generics[key] = param.datatype.makeGeneric(key)
						catch e
							console.log "error",e

			let vals = Object.values(generics)
			if vals:length
				tpl:generics ||= '<'+AST.cary(vals).join(',')+'>'


			if @decorators
				tpl:decorators = AST.cary(@decorators).join('\n')

			let str = '''
				@export @default @protected @static @async @get @set @function @yield@key@generics(@params)@{:@returnType} @code
			'''

			if tpl:static and returnType and returnType.hasSelfReference

				tpl:returnType = returnType.withReplacedThis('self')
				str = '''
				// @ts-ignore
				@protected @static @async @function @yield@key<self extends abstract new (...args: any) => any>(this:self@{, @params})@{:@returnType};
				@protected @static @async @get @set @function @yield@key(@params) @code
				'''

			if @decorators
				tpl:decorators = AST.cary(@decorators).join('\n')
				str = '@decorators\n' + str

			if isGlobal
				tpl:localName = InternalName.new(@name)
				str += '\ntype @localName = typeof @name;\ndeclare global { var @name : @localName }'

			return TPL tpl,str


		if (option(:inClassBody) or option(:inObject)) and co:as != 'descriptor'
			let prefix = ''
			if self.isGetter
				prefix = M('get',option(:keyword)) + ' '
			elif self.isSetter
				prefix = M('set',option(:keyword)) + ' '

			prefix = "async {prefix}" if option(:async)
			prefix = "{M('static',option(:static))} {prefix}" if option(:static)
			out = "{prefix}{star}{M name, null, as: 'field'}({params.c})"
		
			out += code
			return out

		var func = "({params.c})" + code
		var ctx = context


		var fname = helpers.toValidIdentifier(AST.sym(self.name))
		var tpl = {
			localName: fname
			declareName: fname
		}
			
		if target
			# TODO make this work with SymbolIdentifier
			if fname[0] == '['
				fname = fname.slice(1,-1)
			else
				fname = "'{fname}'"

			if isGetter
				out = "Object.defineProperty({target.c},{fname},\{get: {funcKeyword}{func}, configurable: true\})"
				return out
			elif isSetter
				out = "Object.defineProperty({target.c},{fname},\{set: {funcKeyword}{func}, configurable: true\})"
				return out
			else
				let k = OP('.',target,@name)
				out = "{k.c} = {funcKeyword} {func}"
			
			# should never use non es syntax anymore?
			if o:export
				out = "exports.{o:default ? 'default' : fname} = {out}"
		else
			# Should really use TPL for this too
			

			out = "{M funcKeyword, keyword}{star} {M fname, @name}{func}"
			if o:export
				out = "{M('export',o:export)} {o:default ? M('default ',o:default) : ''}{out}"

		if o:global
			out = "{out}; {scope__.root.globalRef}.{fname} = {fname};"

		if option(:return)
			out = "return {out}"

		out = jsdoc() + out

		if option(:declareOnly) and !STACK.tsc
			return ''

		return out

# Literals should probably not inherit from the same parent
# as arrays, tuples, objects would be better off inheriting
# from listnode.

export class Literal < ValueNode

	def initialize v
		@traversed = no
		@expression = yes
		@cache = null
		@raw = null
		@value = load(v)

	def isConstant
		yes

	def load value
		value

	def toString
		"" + value

	def hasSideEffects
		false

	def shouldParenthesizeInTernary
		no

	def startLoc
		@startLoc or (@value and @value:startLoc && @value.startLoc)

	def endLoc
		@endLoc or (@value and @value:endLoc && @value.endLoc)

export class RawScript < Literal

	def c
		@value

export class Bool < Literal

	# Should keep the real value (yes/no/true/false)?
	def initialize v
		@value = v
		@raw = String(v) == "true" ? true : false

	def cache
		self

	def isPrimitive
		yes

	def truthy
		String(value) == "true"
		# yes

	def js o
		String(@value)

	def c
		STACK.@counter += 1
		# undefined should not be a bool
		String(@value)
		# @raw ? "true" : "false"

	def toJSON
		{type: 'Bool', value: @value}

	def loc
		@value:region ? @value.region : [0,0]

export class Undefined < Literal

	def isPrimitive
		yes

	def isTruthy
		no

	def cache
		self

	def c
		M("undefined",@value)

export class Nil < Literal

	def isPrimitive
		yes

	def isTruthy
		no

	def cache
		self

	def c
		let out = M("null",@value)
		if STACK.tsc and datatype
			out = out + ' as unknown as ' + datatype.c
		return out

export class True < Bool

	def raw
		true

	def isTruthy
		yes

	def c
		M("true",@value)

export class False < Bool

	def raw
		false

	def isTruthy
		no

	def c
		M("false",@value)

export class Num < Literal

	# value is token - should not be
	def initialize v
		@traversed = no
		@value = v

	def toString
		String(@value).replace(/\_/g,'')

	def toNumber
		@number ?= parseFloat(toString)

	def isPrimitive deep
		yes

	def isTruthy
		toNumber != 0

	def negate
		@value = -toNumber
		self

	def shouldParenthesize par = up
		par isa Access and par.left == self

	def js o
		return toString

	def c o
		return super(o) if @cache
		var out = M(toString,@value)
		var par = STACK.current
		var paren = par isa Access and par.left == self
		# only if this is the right part of the access
		paren ? "({out})" : out

	def cache o
		return self unless o and (o:cache or o:pool)
		super(o)

	def raw
		# really?
		JSON.parse(toString)

	def toJSON
		{type: typeName, value: raw}

export class NumWithUnit < Literal
	def initialize v, unit
		@traversed = no
		@value = v
		@unit = unit

	def negate
		set(negate: yes)
		return self

	def c o
		let unit = String(@unit)
		let val = String(@value)

		val = "-{val}" if option(:negate)

		if unit == 'ms'
			val = "{val}"
		elif unit == 'kb'
			val = "({val} * 1024)"
		elif unit == 'mb'
			val = "({val} * 1024 * 1024)"
		elif unit == 'gb'
			val = "({val} * 1024 * 1024 * 1024)"
		elif unit == 's'
			val = "({val} * 1000)"
		elif unit == 'minutes'
			val = "({val} * 60 * 1000)"
		elif unit == 'hours'
			val = "({val} * 60 * 60 * 1000)"
		elif unit == 'days'
			val = "({val} * 24 * 60 * 60 * 1000)"
		elif unit == 'n'
			val = "{val}n"
		elif unit == 'fps'
			val = "(1000 / {val})"
		else
			val = "{val}{unit}"
			val = "'{val}'" unless o and o:unqouted

		if OPTS:sourcemap and (!o or o:mark !== false)
			val = M(val,self)
		return val

	def endLoc
		@unit.endLoc

export class ExpressionWithUnit < ValueNode

	def initialize value, unit
		@value = value
		@unit = unit

	def js o
		let unit = String(@unit)
		# util.unit(@value,STR(@unit)).c
		# let out = typeof @value == 'string' ? @value : @value.c
		"({value.c}+{STR(@unit).c})"

# should be quoted no?
# what about strings in object-literals?
# we want to be able to see if the values are allowed
export class Str < Literal

	def initialize v
		@traversed = no
		@expression = yes
		@cache = null
		@value = v
		# should grab the actual value immediately?

	def isString
		yes

	def isPrimitive deep
		yes

	def raw
		# JSON.parse requires double-quoted strings,
		# while eval also allows single quotes.
		# NEXT eval is not accessible like this
		# WARNING TODO be careful! - should clean up

		@raw ||= String(value).slice(1,-1) # incredibly stupid solution

	def isValidIdentifier
		# there are also some values we cannot use
		raw.match(/^[a-zA-Z\$\_]+[\d\w\$\_]*$/) ? true : false

	def isTemplate
		String(@value)[0] == '`'

	def js o
		String(@value)

	def cache o
		# should never cache basic strings?
		if raw:length > 20
			super

		return self

	def c o
		@cache ? super(o) : (M js, @value, o)

export class TemplateString < ListNode

	def js
		let parts = @nodes.map do |node|
			node isa String ? node : node.c()

		let out = '`' + parts.join('') + '`'
		return out

export class Interpolation < ValueNode

# Currently not used - it would be better to use this
# for real interpolated strings though, than to break
# them up into their parts before parsing
export class InterpolatedString < Node

	def initialize nodes, o = {}
		@nodes = nodes
		@options = o
		self

	def add part
		@nodes.push(part) if part
		self

	def visit
		for node in @nodes
			node.traverse
		self

	def startLoc
		option(:open).startLoc

	def endLoc
		option(:close).endLoc

	def isString
		yes

	def isTemplate
		String(option(:open)) == '`'

	def escapeString str
		str = str.replace(/\n/g, '\\\n')

	def toArray
		let items = @nodes.map do |part,i|
			if part isa Token and part.@type == 'NEOSTRING'
				Str.new('"' + part.@value + '"')
			else
				part

		return items

	def js o, opts

		var kind = String(option("open") or '"')
		if kind:length == 3
			kind = kind[0]
		# creating the string
		if opts and opts:as == 'template'
			var parts = []
			@nodes.map do |part,i|
				if part isa Token and part.@type == 'NEOSTRING'
					parts.push escapeString(part.@value)
				elif part
					parts.push('${',part.c(expression: yes),'}')
			return '`' + parts.join('') + '`'
		else
			var noparen = @noparen
			var parts = []
			var str = noparen ? '' : '('
			@nodes.map do |part,i|
				if part isa Token and part.@type == 'NEOSTRING'
					parts.push(kind + escapeString(part.@value) + kind)
				elif part
					if i == 0
						# force first part to be string
						parts.push('""')
					part.@parens = yes
					parts.push(part.c(expression: yes))

			str += parts.join(" + ")
			str += ')' unless noparen
		return str

# Because we've dropped the Str-wrapper it is kinda difficult
export class Symbol < Literal

	def isValidIdentifier
		raw.match(/^[a-zA-Z\$\_]+[\d\w\$\_]*$/) ? true : false

	def isPrimitive deep
		yes

	def raw
		@raw ||= AST.sym(value.toString.replace(/^\:/,''))

	def js o
		"'{AST.sym(raw)}'"

export class RegExp < Literal

	def isPrimitive
		yes

	def js
		var v = super

		# special casing heregex
		if var m = constants.HEREGEX.exec(v)
			# console.log 'matxhed heregex',m
			var re = m[1].replace(constants.HEREGEX_OMIT, '').replace(/\//g, '\\/')
			return '/' + (re or '(?:)') + '/' + m[2]

		v == '//' ? '/(?:)/' : v

# Should inherit from ListNode - would simplify
export class Arr < Literal

	def load value
		value isa Array ? ArgList.new(value) : value

	def push item
		value.push(item)
		self

	def count
		value:length

	def nodes
		var val = value
		val isa Array ? val : val.nodes

	def splat
		value.some(|v| v isa Splat)

	def visit
		@value.traverse if @value and @value:traverse
		self

	def isPrimitive deep
		!value.some(|v| !v.isPrimitive(yes) )

	def js o
		var val = @value
		return "[]" unless val
		var nodes = val isa Array ? val : val.nodes
		var out = val isa Array ? AST.cary(val) : val.c
		out = "[{out}]"
		if datatype and STACK.tsc
			out = out + ' as ' + datatype.c
		return out

	def hasSideEffects
		value.some(|v| v.hasSideEffects )

	def toString
		"Arr"

	def indented a,b
		@value.indented(a,b)
		self

	def self.wrap val
		Arr.new(val)

# should not be cklassified as a literal?
export class Obj < Literal

	def load value
		value isa Array ? AssignList.new(value) : value

	def visit
		@value.traverse if @value
		self

	def isPrimitive deep
		!value.some(|v| !v.isPrimitive(yes) )

	def js o
		return '{' + value.c + '}'

	def add k, v
		k = Identifier.new(k) if k isa String or k isa Token
		var kv = ObjAttr.new(k,v)
		value.push(kv)
		return kv

	def remove key
		for k in value
			value.remove(k) if k.key.symbol == key
		self

	def keys
		Object.keys(hash)

	def hash
		var hash = {}
		for k in value
			hash[k.key.symbol] = k.value if k isa ObjAttr
		return hash
		# return k if k.key.symbol == key

	# add method for finding properties etc?
	def key key
		for k in value
			return k if k isa ObjAttr and k.key.symbol == key
		null

	def indented a,b
		@value.indented(a,b)
		self

	def hasSideEffects
		value.some(|v| v.hasSideEffects )

	# for converting a real object into an ast-representation
	def self.wrap obj
		var attrs = []
		for own k,v of obj
			if v isa Array
				v = Arr.wrap(v)
			elif v:constructor == Object
				v = Obj.wrap(v)
			# if k isa String
			#	k = LIT(k)
			v = NODIFY(v)

			if k isa String
				k = Identifier.new(k)

			attrs.push(ObjAttr.new(k,v))
		return Obj.new(attrs)

	def toString
		"Obj"

export class NumberLike < ValueNode

	def consume node
		if node == NumberLike or node isa NumberLike
			return self
		super

	def js
		"({@value.c}).valueOf()"

export class ObjAttr < Node

	prop key
	prop value
	prop options

	def initialize key, value, defaults
		@traversed = no
		@key = key
		@value = value
		@dynamic = key isa Op
		@defaults = defaults
		self

	def visit stack, state
		# should probably traverse key as well, unless it is a dead simple identifier
		key.traverse
		value.traverse if value
		@defaults.traverse if @defaults

		let decl = state && state:declaring

		if key isa Ivar
			if !value
				key = Identifier.new(key.value)
				value = OP('.',scope__.context,key)
				if @defaults
					value = OP('=',value,@defaults)
					@defaults = null

		elif key isa Private
			if !value
				value = OP('.',scope__.context,key)
				key = Identifier.new(key.value)

		elif key isa Identifier
			# if state && state:declaring
			# 	key.variable = scope__.register(key.symbol,key)\
			# isnt this rather going to

			if !value
				if decl
					value = scope__.register(key.symbol,key, type: decl)
					value = value.via(key)

					if @defaults
						value = OP('=',value,@defaults)
						@defaults = null
				else
					value = scope__.lookup(key.symbol)
					unless value
						value = OP('.',scope__.context,key)

		self

	def js o
		let key = self.key
		let kjs

		# if key isa Identifier and String(key.@value)[0] == '@'
		# 	key = Ivar.new(key)

		if key isa IdentifierExpression or key isa SymbolIdentifier
			# streamline this interface
			kjs = key.asObjectKey
		elif key isa InterpolatedString
			kjs = "[{key.c}]"
		elif key isa Num or key.isReserved
			kjs = "'{key.c}'"
		elif key isa Str and key.isValidIdentifier
			kjs = key.raw
		else
			kjs = key.c(as: 'key')

		# var k = key.isReserved ? "'{key.c}'" : key.c

		if @defaults
			"{kjs} = {@defaults.c}"
		elif value
			"{kjs}: {value.c}"
		else
			"{kjs}"

	def hasSideEffects
		true

	def isPrimitive deep
		!@value or @value.isPrimitive(deep)

export class ObjRestAttr < ObjAttr

	def js o

		let key = self.key
		if value
			"...{value.c}"
		else
			"...{key.c}"

export class ArgsReference < Node

	# should register in this scope --
	def c
		"arguments"

# should be a separate Context or something
export class Self < Literal

	def initialize value
		@value = value

	def cache
		self

	def reference
		return self

	def visit
		@scope__ = scope__
		@scope__.context
		self

	def js
		var s = @scope__ or scope__
		s ? s.context.c : "this"

	def c
		let out = M(js,@value)
		let typ = STACK.tsc and option(:datatype)
		let gen = STACK.tsc and option(:generics)
		if gen
			# TODO what about sourcemapping?
			out += String(gen)

		if typ
			out = "{out} as any as ({typ.c})"
	
		return out

export class This < Self

	def cache
		self

	def reference
		self

	def visit
		self

	def js
		"this"

# OPERATORS

export class Op < Node

	prop op
	prop left
	prop right

	def initialize o, l, r
		# set expression yes, no?
		@expression = no
		@traversed = no
		@parens = no
		@cache = null
		@invert = no
		@opToken = o
		@op = o and o.@value or o

		if @op == 'and'
			@op = '&&'
		elif @op == 'or'
			@op = '||'
		elif @op == 'not'
			@op = '!'
		@left = l
		@right = r
		return self

	def visit
		@right.traverse if @right and @right:traverse
		@left.traverse if @left and @left:traverse
		return self

	def startLoc
		# what about ++test?
		@startLoc or @left.startLoc
	
	def endLoc
		@endLoc or (@right or @left).endLoc

	def hasTagRight
		if isLogical
			let l = @left.unwrappedNode
			let r = @right.unwrappedNode

			if r isa TagLike
				return yes
			if r isa Op and r.hasTagRight
				return yes
			if r isa Op and r.hasTagRight
				return yes
		return no

	def opToIfTree
		if hasTagRight
			let l = @left.unwrappedNode
			let r = @right.unwrappedNode

			if @op == '&&'
				if l isa Op and l.hasTagRight
					@left.warn "Tag not allowed here"

				l = l.opToIfTree if l isa Op
				r = r.opToIfTree if r isa Op

				if r isa If
					r.test = OP('&&',l,r.test)
					return r

				return If.new(l,Block.new([r])).traverse

			elif @op == '||'
				l = l.opToIfTree if l isa Op

				if l isa If
					return l.addElse(Block.new([r]))
				else
					return If.new(l,Block.new([])).addElse(Block.new([r])).traverse
		return self

	def isExpressable
		# what if right is a string?!?
		!right || right.isExpressable

	def js o
		var out = null

		if STACK.tsc and isBitwise
			if isAssignment
				let typ = String(@op).split('=')
				@op = '='
				@right = OP(typ[0],@left,@right)
			else
				@right = @right.consume(NumberLike) if @right
				@left = @left.consume(NumberLike) if @left

		var op = @op
		let opv = op

		var l = @left
		var r = @right

		# make the left and right consume valueOf

		if op == '!&'
			return "({C l} {M '&',@opToken} {C r})==0"

		elif op == '??'
			return "({C l} {M op,@opToken} {C r})"

		elif op == '|=?'
			return If.ternary(OP('==',Parens.new([OP('&',l,r.cache)]),r),
				FALSE,Parens.new([OP('|=',l,r),TRUE])
			).c

		elif op == '~=?'
			return If.ternary(OP('&',l,r.cache),
				Parens.new([OP('~=',l,r),TRUE]),
				FALSE
			).c

		elif op == '^=?'
			return OP('!!',OP('&',OP('^=',l,r.cache),r)).c

		elif op == '=?'
			r.cache
			return If.ternary(OP('!=',l,r),
				Parens.new([OP('=',l,r),TRUE]),
				FALSE
			).c

		let neg = false

		if op == 'isnt'
			neg = true
			op = 'is'

		if op == 'is'
			let res
			if r instanceof Parens
				l.cache
				res = r.consume(Util.Is.new([l,null]))
			else
				res = Util.Is.new([l,r])
			
			if neg
				return "!({res.c})"
			else
				return "({res.c})"

		l = l.c if l isa Node
		r = r.c if r isa Node

		if l && r
			out ||= "{l} {M op,@opToken} {r}"
		elif l
			let s = @opToken and @opToken:spaced ? ' ' : ''
			out ||= "{M op,@opToken}{s}{l}"

		out

	def isString
		@op == '+' and @left and @left.isString

	def isLogical
		@op == '&&' or @op == '||' or @op == 'or' or @op == 'and'

	def isBitwise
		!!constants.BITWISE_OPERATORS[@op]

	def isAssignment
		!!constants.ASSIGNMENT_OPERATORS[@op]

	def shouldParenthesize
		@parens
		# option(:parens)

	def precedence
		10

	def consume node
	
		if node == NumberLike
			if isBitwise
				return self

		elif node isa Walker
			left.consume(node) if left
			right.consume(node) if right

		if node instanceof Util.Is
			if op == '!'
				left = left.consume(node)
			elif isLogical
				left = left.consume(node) if left
				right = right.consume(node) if right
			elif self isa Access
				return node.clone(self)
				
			return self

		return super if isExpressable

		# TODO can rather use global caching?
		var tmpvar = scope__.declare(:tmp,null,system: yes)
		var clone = OP(op,left,null)
		var ast = right.consume(clone)
		ast.consume(node) if node
		return ast

export class ComparisonOp < Op

	def invert
		# are there other comparison ops?
		# what about a chain?
		var op = @op
		var pairs = [ "==","!=" , "===","!==" , ">","<=" , "<",">=" ]
		var idx = pairs.indexOf(op)
		idx += (idx % 2 ? -1 : 1)
		self.op = pairs[idx]
		@invert = !@invert
		self

	def c
		if left isa ComparisonOp
			left.right.cache
			OP('&&',left,OP(op,left.right,right)).c
		else
			super

	def js o
		var op = @op
		var l = @left
		var r = @right

		l = l.c if l isa Node
		r = r.c if r isa Node
		return "{l} {M op,@opToken} {r}"

export class UnaryOp < Op

	def invert
		if op == '!'
			return left
		else
			super # regular invert

	def isTruthy
		var val = AST.truthy(left)
		return val !== undefined ? (!val) : (undefined)

	def js o
		var l = @left
		var r = @right
		var op = op
		var s = @opToken and @opToken:spaced ? ' ' : ''

		if op == 'not'
			op = '!'

		if op == '!' or op == '!!'
			# l.@parens = yes
			var str = l.c
			var paren = l.shouldParenthesize(self)
			# FIXME this is a very hacky workaround. Need to handle all this
			# in the child instead, problems arise due to automatic caching
			unless (str.match(/^\!?([\w\.]+)$/) or l isa Parens or paren or l isa Access or l isa Call) and !str.match(/[\s\&\|]/)
				str = '(' + str + ')'
			# l.set(parens: yes) # sure?
			"{op}{str}"

		elif left
			"{l.c}{s}{op}"

		else
			"{op}{s}{r.c}"

	def normalize
		return self if op == '!'
		var node = (left || right).node
		# for property-accessors we need to rewrite the ast
		return self

	def consume node
		var norm = normalize
		norm == self ? super : norm.consume(node)

	def c
		var norm = normalize
		norm == self ? super : norm.c

export class InstanceOf < Op

	def js s,o

		if right instanceof Str
			let out = "typeof ({left.c})==={right.c}"
			out = helpers.parenthesize(out) if s.parent isa Op
			return out

		# fix checks for String and Number
		if right isa Parens
			let out = right.consume(Util.Isa.new([STACK.tsc ? left : left.cache,null,@op])).c
			out = helpers.parenthesize(out) # if o.parent isa Op
			return out

		if String(@op) == 'instanceof' or STACK.tsc
			let out = "{left.c} {M('instanceof',@opToken)} {right.c}"
			out = helpers.parenthesize(out) if s.parent isa Op
			return out
		
		return Util.Isa.new([left,right,@op]).js(s,o)

export class TypeOf < Op

	def js o
		"typeof {left.c}"

export class Delete < Op

	def js o
		# TODO this will execute calls several times if the path is not directly to an object
		# need to cache the receiver
		var l = left
		var tmp = scope__.temporary(self, pool: 'val')
		var o = OP('=',tmp,l)
		# FIXME
		return "({o.c},delete {l.c}, {tmp.c})" # oh well
		# var ast = [OP('=',tmp,left),"delete {left.c}",tmp]
		# should parenthesize directly no?
		# ast.c

	def shouldParenthesize
		yes

export class In < Op

	def js s,o

		if right isa Parens
			let out = right.consume(Util.In.new([STACK.tsc ? left : left.cache,null])).c
			out = helpers.parenthesize(out)
			return out

		return Util.In.new([left,right]).js(s,o)

# ACCESS

export class Access < Op

	def initialize o, l, r
		# set expression yes, no?
		@expression = no
		@traversed = no
		@parens = no
		@cache = null
		@invert = no
		@op = o and o.@value or o
		@optok = o
		@left = l
		@right = r
		return self

	get is_namespace
		@left isa VarOrAccess and @left:is_namespace

	def startLoc
		@left isa ScopeContext ? (@right).startLoc : (@left or @right).startLoc

	def endLoc
		@right && @right.endLoc

	def clone left, right
		var ctor = self:constructor
		ctor.new(op,left,right)

	def isRuntimeReference
		if left isa VarOrAccess and left.@variable isa ImbaRuntime
			if right isa Identifier
				return right.toString
			return yes
		return no	
	# def datatype
	#	right:datatype ? right.datatype : null

	def js stack
		var raw = null
		var lft = left
		var rgt = right
		var rgtexpr = null
		var tsc = STACK.tsc

		if lft isa VarOrAccess and lft.@variable isa ImportProxy
			return lft.@variable.access(rgt,lft).c

		if rgt isa Token
			rgt = Identifier.new(rgt)

		var ctx = (lft || scope__.context)
		var pre = ""
		var mark = ''

		let safeop = safechain ? '?' : ''

		unless @startLoc
			@startLoc = (lft or rgt).startLoc

		if lft isa Super and stack.method and stack.method.option('inExtension') and false
			return CALL(
				OP('.',scope__.context,'super$'),
				[rgt isa Identifier ? rgt.toStr : rgt]
			).c()

		if rgt isa Index and rgt.value isa Num
			rgt = rgt.value

		if rgt isa Num
			# FIXME not adding type info
			if rgt.toNumber < 0 and !STACK.tsc
				# TODO Make typescript check if it will viably work
				return safeop ? util.optNegIndex(ctx,rgt).c : util.negIndex(ctx,rgt).c

			return ctx.c + "{safeop ? '?.' : ''}[" + rgt.c + "]"

		# is this right? Should not the index compile the brackets
		# or value is a symbol -- should be the same, no?
		if rgt isa Index and (rgt.value isa Str or rgt.value isa Symbol)
			rgt = rgt.value

		# TODO do the identifier-validation in a central place instead
		if rgt isa Str and rgt.isValidIdentifier
			raw = rgt.raw

		elif rgt isa Symbol and rgt.isValidIdentifier
			raw = rgt.raw

		elif rgt isa InterpolatedIdentifier
			rgt = rgt.value

		elif rgt isa SymbolIdentifier
			yes

		elif rgt isa Identifier and rgt.isValidIdentifier
			raw = rgt.c

		if tsc
			let check = "'{rgt.c(mark: false)}' in {ctx.c}"
			if rgt isa Identifier and rgt.isPredicate and !(lft isa Self)
				let val = "{ctx.c}.{rgt.c}"

				if @call
					return "({check} && {val} instanceof Function && {val})"
				elif @assigns
					return "({check} && {ctx.c}).{rgt.c}"
				else
					return "({check} && {val})"
			elif safeop and rgt isa Identifier
				if @call
					return "(({check}) && {ctx.c}?.{rgt.c} instanceof Function && {ctx.c}.{rgt.c})"
				else
					return "(({check}) && {ctx.c}?.{rgt.c})"

		# really?
		# var ctx = (left || scope__.context)
		var out = if raw
			# see if it needs quoting
			# need to check to see if it is legal
			let opjs = tsc ? M('.',@optok) : '.'
			ctx ? "{safeop}{opjs}{raw}" : raw
		else
			var r = rgt isa Node ? rgt.c(expression: yes, as: 'value') : rgt
			"{safeop ? '?.' : ''}[{r}]"

		# console.log "access up {stack.up}"

		# let typ = datatype
		let up = stack.up
		let typ = tsc and option(:datatype)

		if ctx
			out = ctx.c + out

		if self isa ImplicitAccess
			out = M(out,rgt.@token or rgt.@value)

		# tricky?
		if typ and (!(up isa Assign) or up.right.node == self)
			
			if up isa Block and (self isa ImplicitAccess or lft isa Self)
				out = out + ' as ' + typ.c
			else
				out = out + ' as ' + typ.c

		out = pre + out

		if pre
			out = "({out})"
		return out

	def visit
		let lft = left
		left.traverse if left

		right.traverse if right
		@left ||= scope__.context
		return

	def isExpressable
		true

	def alias
		right isa Identifier ? right.alias : super()

	def safechain
		String(@op) == '?.'

	def cache o
		(right isa Ivar && !left) ? self : super(o)

	def shouldParenthesizeInTernary
		@parens or @cache

	def datatype
		super or @right.datatype

export class ImplicitAccess < Access

# Should change this to just refer directly to the variable? Or VarReference
export class LocalVarAccess < Access

	prop safechain

	def js o
		if right isa Variable and right.type == 'meth'
			return "{right.c}()" unless up isa Call

		return right.c

	def variable
		right

	def cache o = {}
		super(o) if o:force
		self

	def alias
		variable.@alias or super()

export class PropertyAccess < Access

	def initialize o, l, r
		@traversed = no
		@invert = no
		@parens = no
		@expression = no # yes?
		@cache = null
		@op = o
		@left = l
		@right = r
		return self

	def visit
		@right.traverse if @right
		@left.traverse if @left
		return self

	# right in c we should possibly override
	# to create a call and regular access instead

	def js o
		# if var rec = receiver
		# 	var ast = CALL(OP('.',left,right),[]) # convert to ArgList or null
		# 	ast.receiver = rec
		# 	return ast.c

		var up = up
		# really need to fix this - for sure
		# should be possible for the function to remove this this instead?
		var js = "{super(o)}"
		return js

	def receiver
		if left isa Super
			SELF
		else
			null

export class IvarAccess < Access

	def visit
		@right.traverse if @right
		@left ? @left.traverse : scope__.context
		return self

	def cache
		# WARN hmm, this is not right... when accessing on another object it will need to be cached
		return self

export class IndexAccess < Access

	def cache o = {}
		return super if o:force
		right.cache
		self

export class VarAccess < ValueNode

export class VarOrAccess < ValueNode

	def initialize value
		# should rather call up to valuenode?
		@traversed 	= no
		@parens 	= no
		@value 		= value
		@identifier = value
		@token 		= value.@value
		@variable = null
		self

	def isGlobal name
		@variable and @variable.isGlobal(name)

	get is_global
		!@variable or (self:is_class and @variable.@value.isGlobal)

	get is_globalThis
		@variable and @variable == ROOT.GLOBAL

	get is_class
		@variable and @variable.@value instanceof ClassDeclaration

	get is_import
		@variable and @variable:is_import

	get global_interface
		!@variable and GLOBAL_INTERFACES[@token]

	get is_namespace
		# or is import?
		(!@variable and !@isSelf) or self:is_class or self:is_import or self:is_globalThis

	def startLoc
		@token.startLoc

	def endLoc
		@token.endLoc

	# Shortcircuit traverse so that it is not added to the stack?!
	def visit stack, state
		# @identifier = value # this is not a real identifier?
		var variable
		var scope = scope__
		var name = value.symbol

		if state && state:declaring
			# console.log "VarOrAccess {@identifier}"
			variable = scope.register(value,self,type: state:declaring)

		# if name == '$'
		#	if @tagref = stack.up(Tag)
		#		return self

		variable ||= scope.lookup(value.symbol)

		if variable and variable isa GlobalReference
			let name = variable.name

			if variable isa ZonedVariable and !stack.tsc
				@value = variable.forScope(scope)
			elif stack.tsc
				@value = LIT(name)
			elif stack.isNode
				@value = LIT(scope.imba.c)
				if name != 'imba'
					@value = LIT("{scope.imba.c}.{name}")
			else
				@value = LIT(name)

		# does not really need to have a declarator already? -- tricky
		elif variable && variable.declarator
			# var decl = variable.declarator
			let vscope = variable.scope

			# if the variable is not initialized just yet and we are
			# in the same scope - we should not treat this as a var-lookup
			# ie.  var x = x would resolve to var x = this.x() if x
			# was not previously defined
			if vscope == scope and !variable.@initialized

				# here we need to check if the variable exists outside
				# if it does - we need to ensure that the inner variable does not collide
				let outerVar = scope.parent.lookup(value)
				if outerVar
					variable.@virtual = yes
					variable.@shadowing = outerVar
					variable = outerVar

			# should do this even if we are not in the same scope?
			# we only need to be in the same closure(!)

			if variable and variable.@initialized or (scope.closure != vscope.closure)
				@variable = variable
				variable.addReference(self)
				@value = variable # variable.accessor(self)
				@token.@variable = variable
				# if vscope isa RootScope and vscope.context != scope.context and variable.type == 'meth'
				# 	warn "calling method from root scope {value} is deprecated - see issue #112"
				return self
			# FIX
			# @value.safechain = safechain
		elif value.symbol == 'self'
			@value = scope.context
			@isSelf = yes

		elif !@identifier.isCapitalized
			let selfvar = scope.lookup('self')
			let ctx = scope.context
			if !selfvar and ctx.isGlobalContext
				@includeType = yes
			else
				@isSelf = yes

				if value.symbol == 'constructor' and true
					let cls = STACK.up(ClassDeclaration)
					# console.log "Constructor reference!!",!!cls
					datatype ||= ConstructorType.new(cls)
					# are we really sending this datatype in here??

				@value = ImplicitAccess.new(".",(Self.new).traverse,@value) # .set(datatype: datatype)
		else
			@isGlobal = yes
		
		return self

	def js o

		if @tagref
			return @tagref.ref
		
		let val = @variable or @value

		if STACK.tsc
			let typ = datatype
			let generics = option('generics')
			let out = val.c

			# console.log self,@options

			if generics
				out += generics.c

			if typ
				if val.datatype == typ
					val.datatype = null

				if datatype.isGeneric
					return out + datatype.c
				else
					# parenthesize
					return helpers.parenthesize(out + ' as unknown as ' + datatype.c)
				
			return out

		return val.c()

	def node
		self
		# @variable ? self : value

	def datatype
		super or @identifier.datatype

	def symbol
		@identifier.symbol

	def cache o = {}
		# @variable ? (o:force ? super(o) : self) : value.cache(o)
		# Dont cache global either
		(@variable or @isGlobal) ? (o:force ? super(o) : self) : super(o)

	def decache
		@variable ? super() : value.decache
		self

	def dom
		value.dom

	def safechain
		@identifier.safechain

	def dump
		{ loc: loc }

	def loc
		var loc = @identifier.region
		return loc or [0,0]

	def region
		@identifier.region

	def shouldParenthesizeInTernary
		@cache or (@value and @value.@cache) or @parens

	def toString
		"VarOrAccess({value})"

	def toJSON
		{type: typeName, value: @identifier.toString}

export class VarReference < ValueNode

	prop variable
	prop declared
	prop type

	def initialize value, type
		if value isa VarOrAccess
			value = value.value
			@variable = null
		elif value isa Variable
			@variable = value
			value = ""

		# for now - this can happen
		super(value)
		@export = no
		self.set(type: type)
		@type = type and String(type)
		@declared = yes # just testing now

	get is_static
		option('static') and !STACK.tsc

	def datatype
		super or (@value:datatype ? @value.datatype : null)

	def loc
		@value.region

	def declare
		self

	def consume node
		# really? the consumed node dissappear?
		forceExpression

		self

	def forceExpression
		unless @expression == true
			@expression = true
			for variable in @variables
				variable.@type = 'let'
				variable.@virtual = yes
				variable.autodeclare()
		return self

	def visit stack, state
		var vars = []
		var virtualize = stack
		let scope = scope__

		@variables = scope.captureVariableDeclarations do
			@value.traverse(declaring: @type, variables: vars)
			# should happen automatically when traversing via traverse(declaring:...)
			if @value isa Identifier
				@value.@variable ||= scope.register(@value.symbol,@value,type: @type, datatype: datatype)

		if self:is_static

			if @variables:length > 1
				warn("Destructuring not supported for static variables", loc: option('static'))

			for item in @variables
				item.proxy(scope.staticsRef,LIT(STACK.getSymbol))
		
		return self

	def js stack,params,plain
		let out = @value.c()
	
		if option(:global) and !plain
			if STACK.tsc
				let pars = {
					js: js(stack,params,yes)
					kind: type
					name: out
				}
				return TPL pars, 'declare global { %js }'
		
		let typ = (STACK.tsc and datatype)

		if typ
			out = TYPED(out,typ)

		if self:is_static
			let rgt = @right ? @right.c(expression: true) : 'null'
			return "{out} ??= {rgt}"

		
		if @right
			let rgt = @right.c(expression: true)
			out += " = {rgt}"

		if @expression
			if @value isa Obj
				out = "({out})"

		else
			if STACK.tsc and @variables:length > 1 and @variables.some(do $1.vartype)
				let kind = @type # or var?
				let js = ''
				for item in @variables
					# no longer supported
					js += "{M(kind,@keyword)} {TYPED(item,item.vartype)};\n"

				if @value isa Obj
					out = "({out})"

				js += "{out}"
				return js

			

			out = "{@type} {out}"
			if option(:export)
				out = "{M('export',option(:export))} {out}"
		return out

# ASSIGN

export class Assign < Op

	def initialize o, l, r
		# set expression yes, no?
		@expression = no
		@traversed = no
		@parens = no
		@cache = null
		@invert = no
		@opToken = o
		@op = o and o.@value or o
		@left = l
		@right = r
		return self

	def isExpressable
		!right || right.isExpressable

	def isUsed
		# really?
		# if up is a block in general this should not be used -- since it should already have received implicit self?
		if up isa Block # && up.last != self
			return no
		return yes

	# FIXME optimize
	def visit
		var l = @left
		var r = @right


		# The special case where setting `item = item` should compile to `self.item = item`
		if l isa VarOrAccess and r isa VarOrAccess and l.@identifier.symbol == r.@identifier.symbol
			@left = l = Access.new(".",scope__.context,l.@value)

		l.@assigns = r
		# console.log "Assign {l} {r}"
		# Regularly, the var is declared after the right side, so `let item = item` resolves to
		# `let item = self.item`. In the case of `let item = do ...` however, the variable
		# must be declared before visiting the inner scope of the function
		if l isa VarReference and r isa Lambda
			l.traverse()

		if r
			r.traverse(assignment: yes)

		if l
			l.traverse()

		if l.datatype and r and !r.datatype
			r.datatype = l.datatype


		# needed for extending global type in tooling
		if l isa Access and l.@left and l.@left.@variable == ROOT.GLOBAL
			# warn about this?
			@globalAssign = yes
		
		# console.log String(STACK),String(l),String(l and l.@left)

		if STACK.tsc
			if l isa Access and l.@left isa VarOrAccess
				let variable = l.@left.@variable
				# console.log variable,l.@left.@value
				# if it is not a var

		if option(:global) and l isa VarReference and !STACK.tsc
			@left = l = OP('.',ROOT.GLOBAL,l.value)

		# if l isa VarOrAccess
		let up = STACK.up
		if l isa VarReference and !(up isa Block) and !(up isa Export) and !(up isa TagBody)
			l.forceExpression

		return self

	def c o
		unless right.isExpressable
			# if left isa VarReference and !(right isa Loop) and false
			#	let ref = left
			#	@left = left.@value
			#	return Block.new([ref,BR,right.consume(self)]).c(o)
			if left isa VarReference and (!(right isa Loop) or @expression)
				left.forceExpression

			return right.consume(self).c(o)

		# testing this
		return super(o)

	def js o,plain

		# return OP('.',ROOT.GLOBAL,)
		if option(:global) and !plain
			if STACK.tsc
				let orig = js(o,true)
				# if not expressable - force wrap in function?
				let id = left.value
				let kind = left.type
				let pars = {
					js: orig
					kind: left.type
					name: id
					localName: InternalName.new(id)
				}

				let out = '''
					%js
					type %localName = typeof %name
					declare global { var %name : %localName }
				'''

				return TPL pars,out

		unless right.isExpressable
			p "Assign#js right is not expressable "
			# here this should be go out of the stack(!)
			# it should already be consumed?
			left.forceExpression if left isa VarReference
			return right.consume(self).c

		if @expression
			left.forceExpression

		var l = left.node
		var r = right
		var lc = null

		# FIXME Not supported anymore?
		if l isa Self
			var ctx = scope__.context
			l = ctx.reference

		if l isa VarReference
			l.@right = r
			return l.c

		# test for typescript namespacing
		if STACK.tsc and l isa Access and l:is_namespace and String(op) == '=' and STACK:is_top_level
			# console.log String(l.@left),String(l.@right),String(STACK) #  l.@left:is_class
			if true
				let ns = l.@left
				let internal = InternalName.new(l,l.@right)
				let iface = ns:global_interface

				let vars = {
					global: !ns.@variable
					sysname: internal
					namespace: l.@left
					name: l.@right
					value: right.c(expression: true)
				}
				Object.assign(vars,iface or {})

				try vars:export = option(:export) or !!ns.@variable.@value.isExported
				try vars:path = ns.@variable.importPath

				let str = if vars:path
				
					'''
					var @sysname = @value
					declare module %path { namespace @namespace { var @name : typeof @sysname } }
					'''
				elif ns:is_globalThis
					'''
					var @sysname = @value
					declare global { var @name : typeof @sysname }
					'''
				elif vars:interface
					# check if it is of the ArrayConstructor type etc
					'''
					var @sysname = @value
					declare global { interface @interface { @name : typeof @sysname } }
					'''
				elif ns:is_global
					# check if it is of the ArrayConstructor type etc
					'''
					var @sysname = @value
					declare global { namespace @namespace { var @name : typeof @sysname } }
					'''
				else
					'''
					var @sysname = @value
					%export declare namespace @namespace { var @name : typeof @sysname }
					'''
				
				return TPL(vars,str)
				# return js
				# lc = "globalThis.{M(helpers.toNamespacedIdentifier('OPS',String(l.@right)),l.@right)}"

		lc ||= l.c

		if STACK.tsc and op == '||='
			op = '  ='
		var out = "{lc} {op} {right.c(expression: true)}"

		# if let typ = (STACK.tsc and (datatype or (l and !(l isa VarReference) and l.datatype)))
		# 	# The datatype should be passed in to the rigth value we are setting instead?
		# 	out = typ.c() + ' ' + out

		if l isa Obj
			out = "({out})"

		return out

	# FIXME op is a token? _FIX_
	# this (and similar cases) is broken when called from
	# another position in the stack, since 'up' is dynamic
	# should maybe freeze up?
	def shouldParenthesize par = up
		@parens or par isa Op && par.op != '='

	def consume node
		if node isa TagLike
			if right isa TagLike
				right.set(assign: left)
				return right.consume(node)
			else
				return self

		if node isa Return and left isa VarReference

			if STACK.tsc
				let rgt = @right
				let vars = @left.@variables
				let after = vars[0] ? VarAccess.new(vars[0]).consume(node) : node
				return Block.new([self,BR,after])
				# return Block.new([OP('=',sysvar,@right),BR,VarAccess.new(@left.@variable).consume(node)])
			left.forceExpression

		if isExpressable
			forceExpression
			return super(node)

		var ast = right.consume(self)
		return ast.consume(node)

export class PushAssign < Assign

	prop consumed

	def register node
		@consumed ||= []
		@consumed.push(node)
		self

	def js o
		"{left.c}.push({right.c})"

	def consume node
		return self

export class TagPushAssign < PushAssign

	def js o
		"{left.c}.push({right.c})"

	def consume node
		return self

export class ConditionalAssign < Assign

export class CompoundAssign < Assign

	# FIXME can we merge consume and js?
	def consume node
		return super if isExpressable

		var ast = normalize
		return ast.consume(node) unless ast == self

		ast = right.consume(self)
		return ast.consume(node)

	def normalize
		var ln = left.node
		# we dont need to change this at all
		unless ln isa PropertyAccess
			return self

		ln.left.cache if ln.left
		# TODO FIXME we want to cache the context of the assignment
		var ast = OP('=',left,OP(op[0],left,right))
		ast.toExpression if ast.isExpressable

		return ast

	def c
		var ast = normalize
		return super if ast == self

		# otherwise it is important that we actually replace this node in the outer block
		# whenever we normalize and override c it is important that we can pass on caching
		# etc -- otherwise there WILL be issues.
		var up = STACK.current
		if up isa Block
			# an alternative would be to just pass
			up.replace(self,ast)
		ast.c

###
Started as aextremely limited jsdoc type annotations. Need to be properly parsed
and handled now that it has expanded to cover much more of TS.
###
export class TypeAnnotation < Node

	def initialize value, source
		@value = value
		@source = source
		@optional = no
		@replaced = null
		self

	def add item
		@parts.push(item)

	def startLoc
		(@source or @value).startLoc

	def endLoc
		(@source or @value).endLoc

	def asParam name
		"@param \{{asRawType}\} {name}"

	def isGeneric
		String(@value)[0] == '<'

	def isOptional
		@optional

	def asGenericNames
		@genericNames ||= MappedString.new(extractGenericNames(plain),self)

	def hasSelfReference
		@value.match(/(^|[\\\[\,\<\(])(typeof )?self([\[\<\]\,\)\>]|$)/g)

	def withReplacedThis val
		c.replace(/(^|[\[\,\<\\\/\(]|typeof )this([\\\/\[\]\,\)\>]|$)/g,"$1" + val + "$2")

	def makeGeneric name
		unless @generic
			asRawType
			@generic = TypeAnnotation.new(@value,@source)

			if @generics
				@generic.@replaced = name
				if @genericWrap
					@replaced = @plain.replace('$$GENERIC$$',name)
			
			elif !@generics
				@generic.@replaced = @genericWrap ? name : name + ' extends (' + @plain + ')'
				@replaced = @genericWrap ? @plain.replace('$$GENERIC$$',name) : name

		return @generic


	def visit
		asRawType
		self

	def plain
		asRawType
		@plain

	def asRawType
		if @rawType
			return @rawType

		let opt = no
		let raw = String(@value)
		
		raw = raw.slice(1) if raw[0] == '\\'

		# unwrap the parens?
		if raw[0] == '(' and raw[raw:length - 1] == ')'
			raw = raw.slice(1,-1)

		let end = raw.slice(-1)

		if end == '?'
			@optional = yes
			raw = raw.slice(0,-1)
			
		raw = raw.replace(/(^|[\[\,])\<([a-z\-\d]+)\>/g) do |m,pre,name|
			return m if isGeneric and !pre
			pre + (TagTypeIdentifier.new(name)).toClassName

		raw = raw.replace(/(^|[\[\,\<\\])self([\[\]\,\)\>]|$)/g) do |m,pre,post|
			pre + "this" + post

		raw = raw.replace(/(^|[\[\,\<\(\s])(\$\d*)(?=[\=\[\s\]\,\)\>]|$)/g) do |m,pre,ref,post|
			if ref == '$'
				@genericWrap = yes
				return pre + '$$GENERIC$$'
	
			@generics ||= Set.new
			@generics.add(ref)
			return m

		@plain = raw

		return @rawType = M(raw,self)

	def asIteratorValue
		wrapDoc(asRawType + '[]')

	def wrapDoc inner
		return '/**@type {' + inner + '}*/'

	def toString
		asRawType

	def c
		if STACK.tsc
			return @replaced ? M(@replaced,self) : asRawType
		else
			console.warn "Not supposed to compile TypeAnnotation when targeting {STACK.platform}",OPTS:sourcePath

		return '/**@type {' + asRawType + '}*/'
		# M(String(@value).slice(1),self)

class ConstructorType < TypeAnnotation
	def initialize val
		@value = val
		@source = val.@name

	def visit
		self

	def c
		"(typeof {@source.c})"

export class Generics < TypeAnnotation

# IDENTIFIERS

# really need to clean this up
# Drop the token?
export class Identifier < Node

	prop safechain
	prop value
	prop variable

	def initialize value
		if value isa Token
			@startLoc = value.startLoc
		@value = load(value)
		@symbol = null

		if ("" + value).indexOf("?") >= 0
			@safechain = yes
		# @safechain = ("" + value).indexOf("?") >= 0
		self

	def isStatic
		yes

	def toRaw
		@value.@value or @value

	def add part
		IdentifierExpression.new(self).add(part)

	def references variable
		@value.@variable = variable if @value
		self

	def metaIdentifier
		Identifier.new('αα' + AST.sym(@value))

	def load v
		return (v isa Identifier ? v.value : v)

	def traverse
		# NODES.push(self)
		self

	def visit

		if @value isa Node
			# console.log "IDENTIFIER VALUE IS NODE"
			@value.traverse
		self

	def region
		[@value.@loc,@value.@loc + @value.@len]

	def startLoc
		@startLoc or @value and @value:startLoc ? @value.startLoc : null

	def endLoc
		@endLoc or @value and @value:endLoc ? @value.endLoc : null

	def loc
		[startLoc,endLoc]

	def isValidIdentifier
		# !STACK.tsc or
		helpers.isValidIdentifier(symbol)

	def isReserved
		@value:reserved or RESERVED_TEST.test(String(@value))

	def isPredicate
		(/\?$/).test(String(@value))

	def isCapitalized
		(/^[A-Z]/).test(String(@value))

	def isInternal
		(/^\$/).test(String(@value))

	def symbol
		@symbol ||= AST.sym(value)

	def toString
		String(@value)

	def toStr
		return Str.new("'" + symbol + "'")

	def toAttrString
		return Str.new("'" + String(@value) + "'")

	def toJSON
		toString

	def alias
		AST.sym(@value)

	def js o
		@variable ? @variable.c() : symbol

	def c o
		# console.log 'compiling identifier as',o and o:as,symbol
		if o
			if o:as == 'value'
				# console.log 'compiling identifier as',o:as,symbol
				return "'{symbol}'"

			if o:as == 'meta'
				return "'{symbol}'"

			if o:as == 'namespaced' and o:ns
				return M("Σ{o:ns}Σ{symbol}",@token or @value)

			if o:as == 'field' and !isValidIdentifier
				return M("['{symbol}']",@token or @value)

			if o:as == 'key' and !isValidIdentifier
				return "'{symbol}'"
		 

		let up = STACK.current
		# look into
		if (up isa Util and !(up isa Util.Iterable) and !(up isa Util.Is)) # not all utils
			return toStr.c

		let out = js
		# FIXME should it not always enable?
		if OPTS:sourcemap and (!o or o:mark !== false)
			out = M(out, @token or @value)

		# if @options and @options:generics and STACK.tsc
		# 	if !CONTEXT:template
		# 		out += M(@options:generics)
		# 		# console.log "Compile identifier with generics! {STACK}",!!CONTEXT:template
		return out

	def dump
		{ loc: region }

	def namepath
		toString

	def shouldParenthesizeInTernary
		@parens or @cache

	def registerVariable type, scope = scope__
		@variable = scope.register(symbol,self,type: type)
		return self

	def resolveVariable scope = scope__
		let variable = scope.lookup(symbol)
		@variable = variable
		return self

export class DecoratorIdentifier < Identifier

	def symbol
		helpers.toValidIdentifier(String(@value))
		# "decorator${@value.slice(1)}"

	def toString
		symbol

export class ImportProxyAccess < Node
	def initialize value
		@value = value

	def toString
		@value

	def asObjectKey
		"[{toString}]"

	def c o = {}
		# console.log 'compiling identifier as',o and o:as,symbol
		if o:as == 'field'
			return "[{@value}]"
		
		return @value

export class SymbolIdentifier < Identifier

	def c o = {}
		if STACK.tsc
			return variable.c
			# return "{@value.slice(0)_$}"
		let out = variable.c
		if o:as == 'field'
			"[{out}]"
		else
			out

	def variable
		@variable ||= scope__.root.symbolRef(@value.slice(0))

	def metaIdentifier
		scope__.root.symbolRef("__" + @value.slice(0))

	def isConstant
		yes

	def asObjectKey
		"[{c}]"

	def toString
		c

	def resolveVariable
		self

	def registerVariable
		self

export class IdentifierExpression < Node
	prop single

	def self.wrap node
		return node
		node isa self ? node : self.new(node)

	def initialize value
		super
		@static = yes
		@nodes = [@single = value]

	def add part
		@nodes.push(part)
		@single = null
		self

	def isPrimitive
		@single and @single isa Token

	def isStatic
		isPrimitive

	def visit
		for node in @nodes when node isa Node
			node.traverse
		self

	def asObjectKey
		if isPrimitive
			"{@single.c()}"
		elif (@single and !option(:prefix))
			"[{@single.c()}]"
		else
			"[{asString}]"

	def startLoc
		let n = @nodes[0]
		n?.startLoc

	def endLoc
		let n = @nodes[@nodes:length - 1]
		n?.endLoc

	def asIdentifier
		(@single and !option(:prefix)) ? "[{@single.c()}]" : "[{asString}]"

	def asString
		# what if a part is a string?	
		let s = '`'
		if option(:prefix)
			s += option(:prefix)
		for node in @nodes
			if node isa Token
				s += node.value
			else
				s += '${'
				s += node.c()
				s += '}'
		s += '`'
		return s

	def toRaw
		@single ? @single.c : ''

	def toString
		toRaw

	def js s, o = {}
		if o:as == 'string' or s.parent isa Util
			return asString
		elif (o:as == 'key' or option(:as) == 'property')
			return asObjectKey
		elif o:as == 'access'
			yes
		elif @single and @single isa Node
			return @single.c(o)
		else
			asString

export class InterpolatedSymbolIdentifier < IdentifierExpression

	def initialize pre, value
		super(value)
		@single = null
		set(prefix: pre.@value)

	def asString
		"Symbol.for({super})"


export class MixinIdentifier < Identifier

	def symbol
		"mixin${@value.slice(1)}"

	def traverse o
		if @traversed
			return self
		# should not really look in the scope
		@mixin = scope__.mixin(@value.slice(1))
		# console.log 'found mixin?!',@mixin
		unless @variable
			resolveVariable
		@traversed = yes

	def c o
		if o and (o:as == 'string' or o:as == 'substr')
			let flags = toFlags.map do |f| f isa Variable ? "$\{{f.c}\}" : f.raw
			let out = flags.join(' ')
			return o:as == 'string' ? "`{out}`" : out

		let up = STACK.current
		return toStr.c if (up isa Util and !(up isa Util.Iterable)) # not all utils
		let out = js
		if OPTS:sourcemap and (!o or o:mark !== false)
			out = M(out, @token or @value)
		return out

	def toString
		symbol

	def toFlagName
		# look for variable etc
		symbol

	def toFlags
		return @parts if @parts
		traverse
		let v = @variable
		let parts = []
		let part = v

		while part
			if part.@declarator isa StyleRuleSet
				parts.push(STR(part.@declarator.@name))
			else
				parts.push(part)

			part = part.@parent

		# for part in vars
		# 	if part.@declarator isa StyleRuleSet
		# 		parts.push(STR(v.@declarator.@name))
		# console.log 'resolvedFlagName',parts
		return @parts = parts

		if v and v.@declarator isa StyleRuleSet
			return v.@declarator.@name
		return null

export class Private < Identifier

	def symbol
		@symbol ||= AST.sym('__' + value)

	def add part
		IdentifierExpression.new(value).add(part).set(prefix: '__', private: yes)

	# def c
	# 	let up = STACK.current
	# 	return toStr.c if up isa Util
	# 	return '' + symbol

export class TagIdRef < ValueNode

	def initialize v
		@value = v isa Identifier ? v.value : v
		self

	def js
		"{scope__.imba.c}.getElementById('{value.c}')"

# This is not an identifier - it is really a string
# Is this not a literal?

# FIXME Rename to IvarLiteral? or simply Literal with type Ivar
export class Ivar < Identifier

	def initialize v
		@value = v isa Identifier ? v.value : v
		self

	def name
		helpers.dashToCamelCase(@value).replace(/^[\#]/,'')
		# value.c.camelCase.replace(/^@/,'')

	def alias
		name

	# the @ should possibly be gone from the start?
	def js o
		return symbol

export class Decorator < ValueNode

	def name
		@name ||= @value.js

	def visit
		@token = @value
		@variable = scope__.lookup(name)
		@value.@variable ||= @variable

		unless @variable
			@value = runtime[name]

		@call.traverse if @call
		if option(:params)
			@params = option(:params)
			@params.traverse

		if let block = up
			block.@decorators ||= []
			block.@decorators.push(self)

	def tscGetter name, content = null
		let out = @value.c

		if @params
			out += "({@params.c(expression: yes)})"
		else
			out += "()"

		if content
			out += ".wrap({content})"

		return out

	def c
		# should return other places as well...
		return if STACK.current isa ClassBody

		let out

		if STACK.tsc
			out = '@' + M(@value,@token)
		else
			out = @value.c

		if @params
			out += ".bind([{@params.c(expression: yes)}])"
		else
			out += ".bind([])"
		return out

export class DescriptorPart < Node
	prop params
	prop value
	prop context

	def initialize value, owner
		@name = value

	def visit stack
		if params
			params.traverse
		if value
			value.traverse
		self

	def js
		if context
			let op = OP('.',context,@name)
			let path = op

			if value
				return OP('=',path,value).c

			let fn = OP('isa',path,LIT('Function'))
			let pars = params or (value ? [value] : [])
			let val = params and params.first or value or TRUE
			let call = CALL(path,pars)
			let setter = OP('=',path,val)

			if STACK.tsc
				pars.push(LIT('true')) if pars:length == 0
				return call.c

			return IF(fn,call,setter).c

export class Descriptor < Node

	prop name
	prop value
	prop params

	def initialize value, owner
		if value isa Token
			@name = @value = DecoratorIdentifier.new(value)
		else
			@value = value
			@value.@parens = yes

		@chain = []
		@special = no
		@params = null
		self

	def isSpecial
		@special

	def visit stack
		let pre = stack.@descriptor
		stack.@descriptor = self
		if @name
			# console.log "Descriptor scope {scope__}"
			@variable = scope__.lookup(@name.js)
			@value.@variable ||= @variable

			unless @variable
				let cls = stack.up(ClassDeclaration)
				if STACK.tsc and cls and cls.isExtension
					@selfValue = LIT('self')
					@value = OP('.',@selfValue,@name)
				else
					@value = OP('.',THIS,@name)

		else
			@value.traverse if @value

		# @call.traverse if @call
		@params.traverse if @params
		@chain.map(|v| v.traverse )

		if @callback = option(:callback)
			# make sure it is not bound to the actual target
			@callback.traverse

		if option(:default)
			@default = option(:default)
			if @default isa Literal
				@literal = @default

			unless @default isa Func
				@default = Func.new([],[@default],null,{})
			@default.traverse

		stack.@descriptor = pre

	def valueIsStatic
		!value or value.isPrimitive or (value isa Func and !value.nonlocals)

	def isStatic
		valueIsStatic

	def isProxy
		false

	def add item, type
		if item isa ArgList
			if item.@generated
				# extract options etc
				set(callback: item)
				# let part = DescriptorPart.new(KEY('callback'))
				# part.params = item
				# @chain.push(@last = part)
				
			else
				# console.log 'add',item.@generated
				if type == '='
					# notify if multiple nodes?!
					(@last or self).value = item.@nodes[0]
				else
					(@last or self).params = item or ListNode.new([])
		else
			@chain.push(@last = DescriptorPart.new(item))
		return self

	def js
		let ref = scope__.root.declare('desc',null,system: yes)
		let tsc = STACK.tsc

		let val = @variable ? Instantiation.new(CALL(@value,params or [])) : (@name ? CALL(@value,params or []) : @value)

		let parts = AST.blk([])

		for item in @chain
			item.context = ref
			parts.push(item)

		if @default
			parts.add LIT("{ref.c}.default = {@default.c}")
		if @literal
			# TODO rename to primitive instead
			parts.add LIT("{ref.c}.default.literal = {@literal.c}")

		if @callback and !tsc
			parts.add LIT("{ref.c}.callback = {@callback.c}")

		if tsc
			parts.add(LIT("${ref.c}"))
			# if mixin?
			let slf = (@selfValue || THIS)
			return "((self,${ref.c}={val.c(mark: yes)},{ref.c}=imba.descriptor(${ref.c}))=>({parts.c}))({slf.c})"

		parts.add(ref)
		parts.unshift(OP('=',ref,val))

		return '(' + parts.c(expression: yes) + ')'

# Ambiguous - We need to be consistent about Const vs ConstAccess
# Becomes more important when we implement typeinference and code-analysis
export class Const < Identifier

	def symbol
		# console.log "Identifier#symbol {value}"
		@symbol ||= AST.sym(value)

	def js o
		@variable ? @variable.c : symbol

	def traverse
		if @traversed
			return self

		@traversed = true
		var curr = STACK.current
		if !(curr isa Access) or curr.left == self
			if symbol == "Imba"
				@variable = scope__.imba
			else
				@variable = scope__.lookup(value)
		self

	def c
		if option(:export)
			"exports.{@value} = " + js
		else
			super

export class TagTypeIdentifier < Identifier

	prop name
	prop ns

	def initialize value
		@token = value
		@value = load(value)
		self

	def startLoc
		@token?.startLoc

	def endLoc
		@token?.endLoc

	def toFunctionalType
		let sym = Identifier.new(@token)
		sym = VarOrAccess.new(sym) unless isClass
		return sym

	def load val
		@str = ("" + val)
		var parts = @str.split(":")
		@raw = val
		@name = parts.pop
		@ns = parts.shift # if any?
		return @str

	def traverse o
		if @traversed
			return self
		# NODES.push(self)
		@traversed = yes
		if isClass
			if o and o:declaring
				registerVariable('const',o:declscope or STACK.scope)
				if @variable
					@variable.value = o:declaring
			else
				resolveVariable
		self

	def js o
		return "'" + toNodeName + "'"

	def c
		js

	def func
		var name = @name.replace(/-/g,'_').replace(/\#/,'')
		name += "${@ns.toLowerCase}" if @ns
		name

	def nativeCreateNode
		let doc = scope__.root.document.c
		if isSVG
			CALL(LIT("{doc}.createElementNS"),[STR("http://www.w3.org/2000/svg"),STR(name)])
		else
			CALL(LIT("{doc}.createElement"),[STR(name)])

	def isClass
		!!@str.match(/^[A-Z]/)

	def isLowerCase
		!@name.match(/^[A-Z]/)

	def isNative
		!@ns and TAG_TYPES.HTML.indexOf(@str) >= 0

	def isNativeHTML
		(!@ns or @ns == 'html') and TAG_TYPES.HTML.indexOf(@name) >= 0

	def isNativeSVG
		@ns == 'svg' and TAG_TYPES.SVG.indexOf(@str) >= 0

	def isSVG
		@ns == 'svg' or (!isNative and !@ns and TAG_NAMES["svg_{@str}"])

	def isAsset
		false

	def toAssetName
		isAsset ? @str : null # .slice(4)

	def symbol
		@str

	def isCustom
		!isNative and !isNativeSVG

	def isComponent
		!isNative and !isNativeSVG

	def toSelector
		toNodeName

	def resolveVariable scope = scope__
		let variable = scope__.lookup(@str)
		@variable = variable
		return self

	def toVarPrefix
		let str = @str
		str.replace(/[\:\-]/g,'')

	def toExtensionName
		"Γ{helpers.toValidIdentifier(@str)}"

	def toClassName
		let str = @str
		if str == 'element'
			return 'Element'
		elif str == 'component'
			return 'imba.Component'
		elif str == 'svg:element'
			return 'SVGElement'
		elif str == 'htmlelement'
			return 'HTMLElement'
		elif str == 'fragment'
			return 'DocumentFragment'

		let match = TAG_NAMES[isSVG ? "svg_{@name}" : @name]
		return match:name if match

		if @str == 'fragment'
			return 'DocumentFragment'
		elif isClass
			return @str
		elif STACK.tsc
			return "Γ{helpers.toValidIdentifier(@str)}"
			return helpers.pascalCase(@str + '-custom-element')
			return @str.replace(/\-/g,'_') + '$$TAG$$'
		else
			return helpers.pascalCase(@str + '-component')

	def toTscName
		@str.replace(/\-/g,'_') + '$$TAG$$'

	def sourceId
		@sourceId ||= STACK.sourceId + '-' + STACK.generateId('tag')

	def toNodeName
		if isClass
			@nodeName ||= helpers.dasherize(@str + '-' + sourceId)
		else
			@str

	def toTypeArgument
		if @variable
			@variable.c
		else
			name

	def id
		var m = @str.match(/\#([\w\-\d\_]+)\b/)
		m ? m[1] : null

	def flag
		"_" + name.replace(/--/g,'_').toLowerCase

	def sel
		".{flag}" # + name.replace(/-/g,'_').toLowerCase

	def string
		value

	def toString
		value

export class InterpolatedIdentifier < ValueNode
	def js
		"[{value.c}]"

export class Argvar < ValueNode

	def c
		# NEXT -- global.parseInt or Number.parseInt (better)
		var v = parseInt(String(value))
		# FIXME Not needed anymore? I think the lexer handles this
		var out = "arguments"
		if v > 0
			var s = scope__
			# params need to go up to the closeste method-scope
			var par = s.params.at(v - 1,yes)
			out = "{AST.c(par.name)}" # c

		M(out, @token or @value)

# CALL

export class DoPlaceholder < Node
	def initialize token
		@value = token

	def loc
		@value.loc

	def c
		error("Function missing for & placeholder")

export class AmperRef < ValueNode

	def visit stack
		
		let blk = stack.up do |v|
			v isa ArgList or v isa Block or v isa AmperFunc or v isa Return or v isa TagAttr

		let base = stack.relative(blk,1)
		let inner = stack.relative(base,1)

		if base isa Assign and stack.@nodes.indexOf(base.left) == -1
			unless base.right isa AmperFunc
				let rgt = base.right
				let wrapper = base.right = AmperFunc.new([],rgt)
			return self

		unless base isa AmperFunc
			let wrapper = AmperFunc.new([],base)
			blk.replace(base,wrapper)
		self

	def c
		"v$"

export class TaggedTemplate < Node

	prop value
	prop string

	def initialize value,string
		@value = value
		@string = string

	def visit
		@value.traverse if @value isa Node
		@string.traverse

		if !@string.isTemplate
			@string.warn("Only `` strings allowed in template literals")
		self

	def js
		@value.c + @string.c(as: 'template')
		# @value.c + TemplateString.new(@string.@nodes).c(as: 'template')

	# def c
	#	"HELLO"

export class Call < Node

	prop callee
	prop receiver
	prop args
	prop block

	def initialize callee, args, opexists
		@traversed = no
		@expression = no
		@parens = no
		@cache = null
		@receiver = null
		@opexists = opexists
		# some axioms that share the same syntax as calls will be redirected from here
		if callee isa BangCall
			callee = callee.@callee

		if callee isa Super
			callee.args = self isa BangCall ? [] : args
			return callee

		if callee isa VarOrAccess
			var str = callee.value.symbol
			if str == 'extern'
				callee.value.value.@type = 'EXTERN'
				return ExternDeclaration.new(args)
			if str == 'tag'
				# console.log "ERROR - access args by some method"
				return TagWrapper.new(args and args:index ? args.index(0) : args[0])
			if str == 'export'
				return Export.new(args)

			if str == 'rescue'
				return RescueFunc.new([],args)

		@callee = callee	
		@args = args or ArgList.new([])

		if args isa Array
			@args = ArgList.new(args)

		if callee isa Decorator
			callee.@call = self
			return callee

		return self

	def visit
		args.traverse
		callee.traverse
		# if the callee is a PropertyAccess - better to immediately change it

		let runref = callee.isRuntimeReference

		if callee isa Access and callee.left.isGlobal('import')
			let arg = args.first
			let kind = callee.right.toString

			if arg isa Str
				# TODO remove need for ImbaAsset type
				# callee = runtime:asset
				callee = LIT('')
				let asset = STACK.root.registerAsset(arg.raw,"{kind}",self,arg)
				args.replace(arg,asset:ref)

		elif callee.isGlobal('import')

			# TODO support interpolated strings that can be globbed
			let arg = args.first
			let path = arg isa Str and arg.raw
			# console.log 'calling the global import!!',path
			if path
				let ext = path.split('.').pop()
				# TODO only allow specific kinds after ?
				if EXT_LOADER_MAP[ext] or path.indexOf('?') >= 0

					@asset = STACK.root.registerAsset(path,'',self,arg)
					args.replace(arg,@asset:ref)

		elif callee.isGlobal('require')
			let arg = args.first
			let path = arg isa Str and arg.raw

		if runref == 'asset'

			let arg = args.first

			if arg isa Str
				let asset = STACK.root.registerAsset(arg.raw,'asset',self)
				args.replace(arg,asset:ref)
			# console.log 'visit Call asset',runref

		@block && @block.traverse

		if self isa BangCall and (@args.count == 0) and option(:keyword)
			let bang = option(:keyword)
			@args.setEnds(bang,bang)
		self

	def addBlock block
		var pos = @args.filter(|n,i| n isa DoPlaceholder )[0]
		if pos
			pos.@block = block
		pos ? args.replace(pos,block) : args.push(block)
		self

	def receiver
		@receiver ||= (callee isa Access && callee.left || NULL)

	# check if all arguments are expressions - otherwise we have an issue

	def safechain
		callee.safechain # really?

	def shouldParenthesizeInTernary
		@parens or safechain or @cache

	def startLoc
		@startLoc or @callee and @callee:startLoc ? @callee.startLoc : 0

	def endLoc
		@endLoc or (@args and @args.endLoc) or @callee.endLoc

	def js o
		if @asset
			return @asset:ref.c

		var opt = expression: yes
		var rec = null
		# var args = AST.compact(args) # really?
		var args = args

		# drop this?

		var splat = args.some do |v| v isa Splat

		var out = null
		var lft = null
		var rgt = null
		var wrap = null

		var callee = @callee = @callee.node # drop the var or access?

		var variable = callee.@variable

		if callee isa Access
			callee.@call = self
			lft = callee.left
			rgt = callee.right

		if callee isa Super
			if let m = STACK.method
				if m.option('inExtension')
					callee = OP('.',callee,m.name)
					@receiver = scope__.context

			self
			# return "supercall"

		# never call the property-access directly?
		if callee isa PropertyAccess # && rec = callee.receiver
			@receiver = callee.receiver
			callee = @callee = Access.new(callee.op,callee.left,callee.right)

		if variable === ROOT.ASSERT and !splat and args.first
			args.@nodes[0] = AssertionNode.new(args.first)

		if variable === ROOT.EQ and !splat and args.count >= 2
			let actual = args.index(0)
			let expected = args.index(1)
			let src = STACK.SOURCECODE
			let loc = self.loc
			let aloc = actual.loc
			let eloc = expected.loc
			let source = JSON.stringify(src.slice(loc[0],loc[1]))
			let asource = JSON.stringify(src.slice(aloc[0],aloc[1]))
			let esource = JSON.stringify(src.slice(eloc[0],eloc[1]))
			let actualValue = actual.cache.c(o)
			let expectedValue = expected.cache.c(o)
			let payload = [
				"source:{source}",
				"actual:\{source:{asource},value:{actualValue}\}",
				"expected:\{source:{esource},value:{expectedValue}\}"
			].join(',')
			let callargs = [
				actual.c(expression: yes,mark: false),
				expected.c(expression: yes,mark: false)
			]
			var i = 2
			while i < args.count
				let arg = args.index(i)
				callargs.push(arg.c(expression: yes,mark: false)) if arg
				i++
			return "(globalThis.IMBA_EQ=\{{payload}\},{callee.c(expression: yes)}({callargs.join(',')}))"

		# INFO DEVLOGS
		if variable === ROOT.L
			let dim = '\x1b[90m'
			let bg-blue = '\x1b[44m'
			let bg-green = '\x1b[42m'
			let bg-grey = '\x1b[100m'
			let bg-black = '\x1b[40m'
			let white = '\x1b[97m'
			let green = '\x1b[32m'
			let black = '\x1b[30m'
			let reset = '\x1b[0m'

			callee = LIT("globalThis.DEBUG_IMBA && console.debug")
			let src = STACK.SOURCECODE

			let web = STACK.isWeb
			let fmts = []
			let result = []

			if web
				fmts.push LIT("%c%s%c")
				result.push LIT("'color:gray'")
				result.push LIT("'::'")
				result.push LIT("'color:white'")
			else
				fmts.push LIT("%s")
				result.push LIT("'{dim}::{reset}'")

			let devargs = []
			let devlog = STACK.option(:devlog) || false

			for arg in args
				let [start, end] = arg.loc
				let s = src.slice(start,end)
					.replace(/\\/g, '\\\\')
					.replace(/"/g, '\\"')
					.replace(/'/g, "\\'")
					.replace(/\n/g, "\\n")
					.replace(/\r/g, "\\r")
					.replace(/\t/g, "\\t")

				if devlog
					# If it is a literal

					if arg isa Str or arg isa Num or arg isa Bool
						devargs.push(LIT("''"))
					else
						devargs.push(STR(s))

					devargs.push(arg)

				else
					if arg isa Literal and !(arg isa Self or arg isa This or arg isa Arr)
						if web
							fmts.push LIT("%c%s%c")
							result.push LIT("'background-color:black;color:#56ff89'")
							result.push LIT("'{s}'")
							result.push LIT("'background-color:none;color:white'")
						else
							fmts.push LIT("%s")
							result.push LIT("'{bg-black}{green}{s}{reset}'")
					else
						if web
							fmts.push LIT("%c%s%c")
							result.push LIT("'background-color:#4c73e8;color:white'")
							result.push LIT("'{s}'")
							result.push LIT("'background-color:none;color:white'")
							fmts.push LIT("%o")
						else
							fmts.push LIT("%s")
							result.push LIT("'{bg-blue}{white}{s}{reset}'")
							fmts.push LIT("%O")
						result.push arg
			
			if devlog
				let pars = [Arr.new(devargs),scope__.context]
				let meth = STACK.up(MethodDeclaration)
				pars.push(STR meth.rawName) if meth
				let l = scope__.root.l
				STACK.use('devlog')
				return "(globalThis.DEBUG_IMBA && (l$={STDCALL('devlog$',pars).c},l$ && console.debug(...l$)))"

			else
				args = ArgList.new ["\"{fmts.join(' ')}\"", *result]

		let safeop = ''
		if callee isa Access and callee.op == '?.'
			safeop = '?.'

		# should just force expression from the start, no?
		if @receiver
			# quick workaround
			@receiver.cache unless @receiver isa ScopeContext
			args.unshift(receiver)
			# should rather rewrite to a new call?
			out = "{callee.c(expression: yes)}.call({args.c(expression: yes,mark: false)})"

		else
			let outargs = "({args.c(expression: yes,mark: false)})"
			out = "{callee.c(expression: yes)}{safeop}{M(outargs,@args)}"

		if wrap
			# we set the cachevar inside
			if @cache
				@cache:manual = yes
				out = "({cachevar.c}={out})"

			out = [wrap[0],out,wrap[1]].join("")

		return out

export class BangCall < Call

export class Instantiation < ValueNode

	def self.for value, keyword
		if value isa Tag
			return value.set(unmemoized: keyword)
		else
			return self.new(value).set(keyword: keyword)

	def js o
		"{M('new',keyword)} {value.c}"

export class New < Call

	def visit
		keyword.warn 'Value.new is deprecated - use new Value'
		super

	def js o
		var target = callee

		while target isa Access
			let left = target.left

			if (left isa PropertyAccess) or (left isa VarOrAccess)
				callee.@parens = yes
				break

			target = left

		# var out = "{M(keyword or 'new',keyword)} {callee.c}"
		var out = "{M('new',keyword)} {M(callee.c,callee)}"
		out += '()' unless (o.parent isa Call or o.parent isa BangCall)
		out

export class ExternDeclaration < ListNode

	def visit
		nodes = map do |item| item.node # drop var or access really
		# only in global scope?
		var root = scope__
		for item in nodes
			var variable = root.register item.symbol, item, type: 'global'
			variable.addReference(item)
		self

	def c
		"// externs"

# FLOW

export class ControlFlow < Node

	def loc
		@body ? @body.loc : [0,0]

export class ControlFlowStatement < ControlFlow

	def isExpressable
		no

export class If < ControlFlow

	prop test
	prop body
	prop alt
	prop scope
	prop prevIf

	def self.ternary cond, body, alt
		# prefer to compile it this way as well
		var obj = If.new(cond, Block.new([body]), type: '?')
		obj.addElse Block.new([alt])
		return obj

	def addElse add
		if alt && alt isa If
			alt.addElse(add)
		else
			self.alt = add
			if add isa If
				add.prevIf = self
		self

	def initialize cond, body, o = {}
		setup
		@test = cond # (o:type == 'unless' ? UnaryOp.new('!',cond,null) : cond)
		@body = body
		@alt  = null
		@type = o:type
		invert if @type == 'unless'
		@scope = IfScope.new(self)
		self

	def loc
		@loc ||= [@type ? @type.@loc : 0,body.loc[1]]

	def invert
		if @test isa ComparisonOp
			@test = @test.invert
		else
			@test = UnaryOp.new('!',@test,null)

	def visit stack
		var alt = alt
		var scop = @scope
		scop.visit if scop

		if test
			# make sure variables are registered in outer scope
			@scope = null
			test.traverse
			@scope = scop

		@tag = stack.@tag

		# console.log "vars in if",Object.keys(@scope.varmap)
		# TODO deal with variable scope
		for own name, variable of @scope.varmap
			if variable.type == 'let'
				# console.log "variable virtualize"
				variable.@virtual = yes
				variable.autodeclare()

		# the let-variables declared in if(*test*) should be
		# local to the inner scope, but will technically be
		# declared in the outer scope. Must get unique name

		if !stack.isAnalyzing and !stack.tsc
			@pretest = AST.truthy(test)

			if @pretest === true
				# only collapse if condition includes compiletime flags?
				alt = @alt = null
				if test isa EnvFlag
					@preunwrap = true

			elif @pretest === false
				loc # cache location before removing body
				body = null

		body.traverse if body

		# should skip the scope in alt.
		if alt
			STACK.pop(self)
			alt.@scope ||= BlockScope.new(alt)
			alt.traverse
			STACK.push(self)

		# force it as expression?
		toExpression if @type == '?' and isExpressable
		self

	def js o
		var body = body
		# would possibly want to look up / out
		var brace = braces: yes, indent: yes

		if @pretest === true and @preunwrap
			# what if it is inside expression?
			let js = body ? body.c(braces: !!prevIf) : 'true'

			unless prevIf
				js = helpers.normalizeIndentation(js)

			if o.isExpression
				js = '(' + js + ')'

			return js

		elif @pretest === false and false
			alt.prevIf = prevIf if alt isa If
			let js = alt ? alt.c(braces: !!prevIf) : ''

			unless prevIf
				js = helpers.normalizeIndentation(js)

			return js

		if o.isExpression

			if test?.shouldParenthesizeInTernary
				test.@parens = yes

			var cond = test.c(expression: yes) # the condition is always an expression

			var code = body ? body.c : 'true' # (braces: yes)

			if body and body.shouldParenthesizeInTernary
				code = '(' + code + ')' # if code.indexOf(',') >= 0

			if alt
				var altbody = alt.c
				if alt.shouldParenthesizeInTernary
					altbody = '(' + altbody + ')'

				return "{cond} ? {code} : {altbody}"
			else
				# again - we need a better way to decide what needs parens
				# maybe better if we rewrite this to an OP('&&'), and put
				# the parens logic there
				# cond should possibly have parens - but where do we decide?
				if @tag
					return "{cond} ? {code} : void(0)"
				else
					return "{cond} && {code}"
		else
			# if there is only a single item - and it is an expression?
			var code = null
			var cond = test.c(expression: yes) # the condition is always an expression

			# if body.count == 1 # dont indent by ourselves?

			if body isa Block and body.count == 1 and !(body.first isa LoopFlowStatement)
				body = body.first

			# if body.count == 1
			#	p "one item only!"
			#	body = body.first

			code = body ? body.c(braces: yes) : '{}' # (braces: yes)

			# don't wrap if it is only a single expression?
			var out = "{M 'if',@type} ({cond}) " + code # ' {' + code + '}' # '{' + code + '}'
			out += " else {alt.c(alt isa If ? {} : brace)}" if alt
			out

	def shouldParenthesize
		!!@parens

	def consume node
		if node isa TagLike
			# now we are reconsuming this
			node.flag(F.TAG_HAS_BRANCHES)

			if node.body == self
				let branches = @body ? [@body] : []
				let alt = @alt

				while alt isa If
					branches.push(alt.@body) if alt.@body
					alt = alt.@alt

				if alt
					branches.push(alt)

				for block,i in branches
					node.@branches.push([])
					block.consume(node)

				return self

			if node isa TagLoopFragment
				if @body
					@body = @body.consume(node)

				if @alt
					@alt = @alt.consume(node)
				return self
			else
				return node.register(self)

			return self

		if node isa TagPushAssign or node isa TagFragment
			node.register(self)
			@body = @body.consume(node) if @body
			@alt = @alt.consume(node) if @alt
			return self

		# special case for If created from conditional assign as well?
		# @type == '?' and
		# ideally we dont really want to make any expression like this by default
		var isRet = node isa Return

		# might have been forced to expression already
		# if it was originally a ternary - why not
		if @expression or ((!isRet or @type == '?') and isExpressable)
			toExpression # mark as expression(!) - is this needed?
			return super(node)
		else
			@body = @body.consume(node) if @body
			@alt = @alt.consume(node) if @alt
		self

	def isExpressable
		# process:stdout.write 'x'
		var exp = (!body || body.isExpressable) && (!alt || alt.isExpressable)
		return exp

export class Loop < Statement

	prop scope
	prop options
	prop body
	prop catcher
	prop elseBody

	def loc
		var a = @options:keyword
		var b = @body

		if a and b
			# FIXME does not support POST_ variants yet
			[a.@loc,b.loc[1]]
		else
			[0,0]

	def initialize options = {}
		@traversed = no
		@options = options
		@body = null
		self

	def set obj
		@options ||= {}
		var keys = Object.keys(obj)
		for k in keys
			@options[k] = obj[k]
		self

	def addBody body
		self.body = AST.blk(body)
		self

	def addElse block
		self.elseBody = block
		self

	def isReactive
		@tag and @tag.fragment.isReactive

	def isStatementLike
		yes

	def c o

		var s = stack
		var curr = s.current

		if stack.isExpression or isExpression
			# what the inner one should not be an expression though?
			# this will resut in an infinite loop, no?!?
			scope.closeScope

			var ast = CALL(FN([],[self]),[])
			return ast.c o

		elif stack.current isa Block or (s.up isa Block and s.current.@consumer == self)
			super.c o
		elif @tag
			super.c 0
		else

			scope.closeScope
			var ast = CALL(FN([],[self]),[])
			# scope.context.reference
			return ast.c o
			# need to wrap in function

export class While < Loop

	prop test

	def initialize test, opts
		@traversed = no
		@test = test
		@options = opts or {}
		@scope = WhileScope.new(self)
		# set(opts) if opts
		if option(:invert)
			# "invert test for while {@test}"
			@test = test.invert
		# invert the test

	def visit
		scope.visit
		test.traverse if test
		body.traverse if body

	def loc
		var o = @options
		helpers.unionOfLocations(o:keyword,@body,o:guard,@test)

	# TODO BUG -- when we declare a var like: while var y = ...
	# the variable will be declared in the WhileScope which never
	# force-declares the inner variables in the scope

	def consume node

		# This is never expressable, but at some point
		# we might want to wrap it in a function (like CS)
		return super if isExpressable
		var reuse = no
		# WARN Optimization - might have untended side-effects
		# if we are assigning directly to a local variable, we simply
		# use said variable for the inner res
		# if reuse
		# 	resvar = scope.declare(node.left.node.variable,Arr.new([]),proxy: yes)
		# 	node = null
		# 	p "consume variable declarator!?".cyan
		# else
		# declare the variable we will use to soak up results
		# TODO Use a special vartype for this?
		var resvar = scope.declare('res',Arr.new([]),system: yes)
		# WHAT -- fix this --
		@catcher = PushAssign.new("push",resvar,null) # the value is not preset # what
		body.consume(@catcher) # should still return the same body

		# scope vars must not be compiled before this -- this is important
		var ast = Block.new([self,resvar.accessor]) # should be varaccess instead?
		ast.consume(node)
		# NOTE Here we can find a way to know wheter or not we even need to
		# return the resvar. Often it will not be needed
		# FIXME what happens if there is no node?!?

	def js o
		var out = "while ({test.c(expression: yes)})" + body.c(braces: yes, indent: yes) # .wrap

		if scope.vars.count > 0

			out = scope.vars.c + ';' + out
			# [scope.vars.c,out]
		out

# This should define an open scope
# should rather
export class For < Loop

	def initialize o = {}
		@traversed = no
		@options = o
		@scope = ForScope.new(self)
		@catcher = null

	def loc
		var o = @options
		helpers.unionOfLocations(o:keyword,@body,o:guard,o:step,o:source)

	def ref
		@ref || "{@tag.fragment.cvar}.{oid}"

	def visit stack
		scope.visit

		var parent = stack.@tag

		options:source.traverse # what about awakening the vars here?

		# add guard to body
		if options:guard
			var op = IF(options:guard.invert,Block.wrap([ContinueStatement.new("continue")]))
			body.unshift(op,BR)

		declare

		if options:await
			var fnscope = stack.up(Func) # do |item| item isa MethodDeclaration or item isa Fun

			if fnscope
				set(native: yes)
				fnscope.set(async: yes)

			# TODO Throw if for loop is not for of
			# TODO Throw if inside tag tree - await not supported there

		# here add the things to declare

		if parent
			# TODO remove
			@tag = parent
			stack.@tag = self
			@level = (@tag && @tag.@level or 0) + 1

		body.traverse
		stack.@tag = parent
		self

	def isBare src
		src and src.@variable and src.@variable.@isArray

	def declare
		var o = options
		var scope = scope
		var src  = o:source
		var vars = o[:vars] = {}
		var oi   = o:index
		var params = o:params

		var bare = isBare(src)

		# if the name parameter is array or object we move this inside?

		# what about a range where we also include an index?
		if src isa Range

			let from = src.left
			let to = src.right
			let dynamic = from !isa Num or to !isa Num

			if to isa Num
				vars:len = to
			else
				# vars:len = scope.vars.push(vars:index.assignment(src.left))
				# vars:len = to.cache(force: yes, pool: 'len').predeclare
				vars:len = scope.declare('len',to,type: 'let')
				# to.cache(force: yes, pool: 'len').predeclare

			# scope.vars.push(vars:index.assignment(src.left))
			vars:value = scope.declare(o:name,from,type: 'let')
			vars:value.addReference(o:name) if o:name

			if o:index
				vars:index = scope.declare(o:index,0,type: 'let')
				vars:index.addReference(o:index)
			else
				vars:index = vars:value

			if dynamic
				vars:diff = scope.declare('rd',OP('-',vars:len,vars:value),type: 'let')

		else
			if oi
				vars:index = scope.declare(oi,0,type: 'let')
			else
				vars:index = scope.declare('i',Num.new(0),system: yes, type: 'let', pool: 'counter')

			vars:source = bare ? src : scope.declare('items',util.iterable(src),system: yes, type: 'let', pool: 'iter')

			if params[2]
				vars:len = scope.declare(params[2],util.len(vars:source),type: 'let')
			else
				vars:len = scope.declare('len',util.len(vars:source),type: 'let', pool: 'len', system: yes)

			if o:name
				let op = OP('.',vars:source,vars:index).set(datatype: o:name.datatype)
				o:name.set(datatype: undefined)
				let decl = VarDeclaration.new(o:name,op,'let')
				body.unshift(decl,BR)

			vars:index.addReference(oi) if oi

		return self

	def consume node
		if node isa TagLike
			return node.register(self)

		if isExpressable
			return super

		if @resvar
			var ast = Block.new([self,BR,@resvar.accessor])
			ast.consume(node)
			return ast

		var resvar = null
		var reuseable = no
		var assignee = null

		# this should be autodeclared no?
		resvar = @resvar ||= scope.register(:res,null,system: yes, type: 'var')

		@catcher = PushAssign.new("push",resvar,null) # the value is not preset
		let resval = Arr.new([])
		body.consume(@catcher) # should still return the same body
		resvar.autodeclare() # only if it is a system variable?

		if node isa VarDeclaration or node isa Assign
			node.right = resvar.accessor
			# let block = [self,BR,node]
			return Block.new([
				OP('=',resvar,resval)
				BR,
				self,
				BR,
				node
			])

		elif node
			let block = [OP('=',resvar,resval),BR,self,BR,resvar.accessor.consume(node)]
			return Block.new(block)

		return self

	def js o
		var vars = options:vars
		var idx = vars:index
		var val = vars:value
		var src = options:source

		var cond
		var final

		if src isa Range
			let a = src.left
			let b = src.right
			let inc = src.inclusive

			cond = OP(inc ? '<=' : '<',val,vars:len)
			final = OP('++',val)

			if vars:diff
				cond = If.ternary( OP('>',vars:diff,Num.new(0)), cond, OP(inc ? '>=' : '>',val,vars:len))
				final = If.ternary( OP('>',vars:diff,Num.new(0)),OP('++',val),OP('--',val))

			if idx and idx != val
				final = ExpressionBlock.new([final,OP('++',idx)])

		else
			cond = OP('<',idx,vars:len)

			if options:step
				final = OP('=',idx,OP('+',idx,options:step))
			else
				final = OP('++',idx)

		var before = ""
		var after = ""

		var code = body.c(braces: yes, indent: yes)
		var head = "{M('for',keyword)} ({scope.vars.c}; {cond.c(expression: yes)}; {final.c(expression: yes)}) "

		return before + head + code + after

export class ForIn < For

export class ForOf < For
	prop source

	def declare
		var o = options
		var vars = o:vars = {}
		var params = o:params
		var k
		var v

		# possibly proxy the index-variable?

		if o:own # and !STACK.tsc
			
			if STACK.tsc
				o:value = o:index
				let declvars = scope__.captureVariableDeclarations do
					for par in o:params
						par.traverse(declaring: 'let')
						if par isa Identifier
							par.@variable ||= scope__.register(par.symbol,par,type: 'let')
				@declvars = declvars
			else
				vars:source = o:source.@variable || scope.declare('o',o:source, system: true, type: 'let')
				o:value = o:index

				var i = vars:index = scope.declare('i',Num.new(0),system: yes, type: 'let', pool: 'counter')
				# systemvariable -- should not really be added to the map
				var keys = vars:keys = scope.declare('keys',Util.keys(vars:source.accessor),system: yes, type: 'let') # the outer one should resolve first
				var l = vars:len = scope.declare('l',Util.len(keys.accessor),system: yes, type: 'let')
				k = vars:key = scope.declare(o:name,null,type: 'let') # scope.declare(o:name,null,system: yes)

				if o:value isa Obj or o:value isa Arr
					body.unshift(VarDeclaration.new(o:value,OP('.',vars:source,k),'let'),BR)
					vars:value = null
				elif o:value
					v = vars:value = scope.declare(o:value,null,let: yes, type: 'let')

		else
			source = vars:source = util.iterable(o:source) # STACK.tsc ? o:source : 
			vars:value = o:value = o:name # need to visit these to declare them
			let declvars = scope__.captureVariableDeclarations do
				o:value.traverse(declaring: 'let')
				if o:value isa Identifier
					o:value.@variable ||= scope__.register(o:value.symbol,o:value,type: 'let')
			@declvars = declvars

			# need to declare this variable outside the for of
			if o:index
				vars:counter = scope.parent.temporary(null,{},"{o:index}$")
				body.unshift(VarDeclaration.new(o:index,OP('++',vars:counter),'let'),BR)
				self

			if params[2]
				params[2].warn("Length parameter only allowed on for-in loops")

		# TODO use util - why add references already? Ah -- this is for the highlighting
		v.addReference(o:index) if v and o:index
		k.addReference(o:name) if k and o:name

		self

	def js o
		var vars = options:vars
		var osrc = options:source
		var params = options:params
		var src = vars:source
		var k = vars:key
		var v = vars:value
		var i = vars:index

		var code

		if options:own
			if STACK.tsc
				let k = params[0]
				let v = params[1]
				let source = "Object.entries({options:source.c})"

				if v and v.datatype
					source = source + " as unknown as [string,{M(v.datatype)}][]"

				code = "{M('for',keyword)}(let {v ? "[{k.c},{v.c}]" : "[{k.c}]"} of {source})"
				# code = scope.c(braces: yes, indent: yes)
				return code + scope.c(braces: yes, indent: yes)
				# return code + body.c(indent: yes, braces: yes)

			# FIXME are we sure about this?
			if v and v.refcount > 0
				body.unshift(OP('=',v,OP('.',src,k)))

			body.unshift(OP('=',k,OP('.',vars:keys,i)))
			code = body.c(indent: yes, braces: yes) # .wrap
			var head = "{M('for',keyword)} ({scope.vars.c}; {OP('<',i,vars:len).c}; {OP('++',i).c})"
			return head + code

		else

			if STACK.tsc
				let itertype = []
				# get all the variables declared in the let
				# Doesnt work with the single item now
				for item in @declvars
					# need to do it nested in arrays
					if item.vartype
						itertype.push(item.vartype)
						continue
					
				if itertype:length
					src.set(datatype: LIT("Iterable<[{AST.cary(itertype)}]>"))

			# compile to a naive for of loop
			code = scope.c(braces: yes, indent: yes)
			# let inCode = osrc.@variable ? src : (OP('=',src,osrc))
			# it is really important that this is a treated as a statement

			let ofjs = src.c(expression: yes)
			let js = "(let {v.c} of {ofjs})" + code
			if options:await
				js = "{M('await',options:await)} {js}"
			js = "{M('for',keyword)} {js}"

			if vars:counter
				js = "{vars:counter} = 0; {js}"
			return js

	def head
		var v = options:vars
		# skipping head?
		[
			OP('=',v:key,OP('.',v:keys,v:index))
			OP('=',v:value,OP('.',v:source,v:key)) if v:value
		]

# NO NEED?
export class Begin < Block

	def initialize body
		@nodes = AST.blk(body).nodes

	def shouldParenthesize
		isExpression

export class Switch < ControlFlowStatement

	prop source
	prop cases
	prop fallback

	def initialize a,b,c
		@traversed = no
		@source = a
		@cases = b
		@fallback = c

	def visit
		c.traverse for c in cases
		fallback.traverse if fallback
		source.traverse if source
		return

	def consume node
		if node isa TagLike
			if node.body == self
				let branches = @cases.slice(0).concat([@fallback])
				for block,i in branches when block
					node.@branches.push([])
					block.consume(node)
				return self
			return node.register(self)
		# TODO work inside tags (like loops)
		@cases = @cases.map(|item| item.consume(node))
		@fallback = @fallback.consume(node) if @fallback
		self

	def c o
		if stack.isExpression or isExpression
			var ast = CALL(FN([],[self]),[])
			return ast.c o

		super.c(o)

	def js o
		var body = []

		for part in cases
			part.autobreak
			body.push(part)

		if fallback
			body.push("default:\n" + fallback.c(indent: yes))

		"switch ({source.c}) " + helpers.bracketize(AST.cary(body).join("\n"),yes)

export class SwitchCase < ControlFlowStatement

	prop test
	prop body

	def initialize test, body
		@traversed = no
		@test = test
		@body = AST.blk(body)
		@scope = BlockScope.new(self)

	def visit
		scope__.visit
		body.traverse

	def consume node
		body.consume(node)
		self

	def autobreak
		body.push(BreakStatement.new) unless body.last isa BreakStatement
		self

	def js o
		@test = [@test] unless @test isa Array
		var cases = @test.map do |item| "case {item.c}: "
		cases.join("\n") + body.c(indent: yes, braces: yes)

export class Try < ControlFlowStatement

	prop body
	# prop ncatch
	# prop nfinally

	def initialize body, c, f
		@traversed = no
		@body = AST.blk(body)
		@catch = c
		@finally = f

	def consume node
		@body = @body.consume(node)
		@catch = @catch.consume(node) if @catch
		@finally = @finally.consume(node) if @finally
		self

	def visit
		@body.traverse
		@catch.traverse if @catch
		@finally.traverse if @finally
		# no blocks - add an empty catch

	def c o
		if stack.isExpression or isExpression
			var ast = IIFE([self])
			return ast.c o
		
		super.c(o)

	def js o
		var out = "try " + body.c(braces: yes, indent: yes)
		out += " " + @catch.c if @catch
		out += " " + @finally.c if @finally

		unless @catch or @finally
			out += " catch (e) \{ \}"
		out += ";"
		out

export class Catch < ControlFlowStatement

	prop body

	def initialize body, varname
		@traversed = no
		@body = AST.blk(body or [])
		@scope = CatchScope.new(self)
		@varname = varname
		self

	def consume node
		@body = @body.consume(node)
		self

	def visit
		@scope.visit
		@variable = @scope.register(@varname,self,type: 'let', pool: 'catchvar')

		if @body.len == 0
			# @variable.datatype = 'Something'
			let node = @variable.accessor
			let accessor = node
			if STACK.tsc
				node = IF(LIT("{node.c} instanceof Error"),node)

			@body.push(node)

		@body.traverse

	def js o
		# only indent if indented by default?
		"catch ({@variable.c}) " + @body.c(braces: yes, indent: yes)

# repeating myself.. don't deal with it until we move to compact tuple-args
# for all astnodes

export class Finally < ControlFlowStatement

	def initialize body
		@traversed = no
		@body = AST.blk(body or [])

	def visit
		@body.traverse

	def consume node
		# swallow silently
		self

	def js o
		"finally " + @body.c(braces: yes, indent: yes)

# RANGE

export class Range < Op

	def inclusive
		op == '..'

	def c
		"range"

export class Splat < ValueNode

	def js o
		return "...{value.c}"

		var par = stack.parent
		if par isa ArgList or par isa Arr
			"Array.from({value.c})"
		else
			p "what is the parent? {par}"
			"SPLAT"

	def node
		value

# TAGS



export class TagPart < Node

	prop name
	prop value
	prop params

	def initialize value, owner
		@name = load(value)
		@tag = owner
		@chain = []
		@special = no
		@params = null
		self

	def load value
		value

	def isSpecial
		@special

	def visit
		@chain.map(|v| v.traverse )
		@value.traverse if @value
		@name.traverse if @name:traverse
		self

	def quoted
		@quoted ||= (@name isa IdentifierExpression ? @name.asString : helpers.singlequote(@name))

	def valueIsStatic
		!value or value.isPrimitive or (value isa Func and !value.nonlocals)

	def isStatic
		valueIsStatic

	def isProxy
		false

	def add item, type
		if type == TagArgList
			(@last or self).params = item or ListNode.new([])
		else
			@chain.push(@last = TagModifier.new(item))
		return self

	def modifiers
		@modifiers ||= TagModifiers.new(@chain).traverse

	def js
		""

	def ref
		"c$.{oid}"

	def tagRef
		@tagRef or @tag.ref

export class TagId < TagPart

	def js
		"{tagRef}.id={quoted}"

export class TagFlag < TagPart
	prop condition

	def rawClassName
		name.toRaw

	def value
		@name

	def visit
		@chain.map(|v| v.traverse )
		# @value.traverse if @value
		@condition.traverse if @condition
		@name.traverse if @name:traverse

	def isStatic
		!isConditional and (@name isa Token or @name.isStatic or @name isa MixinIdentifier)

	def isConditional
		!!condition

	def js
		if STACK.tsc
			let val = value.c
			return condition ? "[{val},{condition.c}]" : "[{val}]"
		# NOT used anymore
		let val = value.c(as: 'string')
		condition ? "{tagRef}.flags.toggle({val},{condition.c})" : "{tagRef}.classList.add({val})"

export class TagSep < TagPart

export class TagArgList < TagPart

export class TagAttr < TagPart

	def isSpecial
		String(@name) == 'value'

	def startLoc
		@name?.startLoc

	def endLoc
		@value?.endLoc

	def replace base, replacement
		if @value == base
			@value = replacement

	def isStatic
		super and @chain.every(do |item|
			let val = item isa Parens ? item.value : item
			val isa Func ? !val.nonlocals : val.isPrimitive
		)

	def visit
		@chain.map(|v| v.traverse )
		@value.traverse if @value
		@name.traverse if @name:traverse

		let key = @key = String(@name)
		let i = key.indexOf(':')

		if i >= 0
			@ns = key.slice(0,i)
			@key = key.slice(i + 1)

		unless @value
			@autovalue = yes
			@value = STR(key)

		if @chain:length
			@mods = {}
			for m in @chain
				@mods[m.name] = 1

		if @ns == 'bind'
			STACK.use('dom_bind')

		if !@ns and @key == 'ease'
			STACK.use('dom_transitions')

		# console.log 'visit tag attr',@tag and @tag.tagName

		let isAsset = key == 'asset' or (key == 'src' and value isa Str and (/^(style|img|script|svg)$/).test(@tag.tagName))

		if isAsset
			let tagName = @tag.tagName

			let kind = 'asset'
			if tagName == 'svg'
				# kind = 'js' # no kind here?
				kind = ''
			elif tagName == 'img'
				kind = 'img'
			elif tagName == 'script'
				kind = STACK.@options:vite ? 'url&entry' : 'web'
			elif tagName == 'style'
				kind = 'css'

			let path = value isa Str and value.raw
			if path and !path.match(/^(\/|https?\:\/\/)/)
				@asset = STACK.root.registerAsset(path,kind,self,value)

		self

	def ns
		@ns

	def key
		@key

	def mods
		@mods

	def nameIdentifier
		@nameIdentifier ||= Identifier.new(key)

	def modsIdentifier
		@modsIdentifier ||= Identifier.new(key + '__')

	def js o
		# let mods = AST.compileRaw(@mods or null)
		let val = value.c(o)
		let bval = val
		let op = M('=',option(:op))
		let isAttr = (key.match(/^(aria-|data-)/) or key == 'style') or (@tag and @tag.isSVG) or ns == 'html'
		let tagName = @tag and @tag.@tagName
		let tref = @tag.ref

		if @asset
			val = @asset:ref.c

		if STACK.tsc and (isAttr or TAG_GLOBAL_ATTRIBUTES[key])
			return "{tref}.setAttribute('{key}',String({val}))"

		if isAttr
			if (STACK.isNode or ns == 'html') and !@asset
				# TODO don't compile to setAttribute directly for node
				# mark compilation as being only for node?
				STACK.meta:universal = no
				return "{tref}.setAttribute('{key}',{val})"

		if STACK.tsc
			# how do we remove attribute then?
			let path = self.nameIdentifier().c
			if path == 'value' and @tag.@tagName in ['input','textarea','select','option','button']
				# path = 'richValue'
				val = '/**@type {any}*/(' + val + ')'

			let access = "{tref}.{M(path,@name)}"

			return "{M(access,@name)}{op}{@autovalue ? M('true',@value) : val}"

		let key = self.key

		if key == 'tabindex'
			key = 'tabIndex'

		# why delegate differently on node and web?
		# should rather always delegate value to '#value'?
		if key == 'value' and @tag.@tagName in ['input','textarea','select','option','button'] and !STACK.isNode
			key = 'richValue'

		if ns == 'css'
			"{tref}.css$('{key}',{val})"
		elif ns == 'bind'
			let path = PATHIFY(value)

			if path isa Variable
				let getter = "function()\{ return {val} \}"
				let setter = "function(v$)\{ {val} = v$ \}"
				bval = "\{get:{getter},set:{setter}\}"
			elif path isa Array
				bval = "[{val[0].c(o)},{val[1].c(o)}]"

			"{tref}.bind$('{key}',{bval})"

		elif key.indexOf('--') == 0
			let pars = ["'{key}'",val]
			let u = option(:unit)
			let k = StyleTheme.propAbbr(option(:propname))
			if u or k
				pars.push(u ? STR(u) : NULL)
				pars.push(STR(k)) if k

			STACK.use('styles')
			let term = option(:styleterm)
			if term and term:param
				while pars:length < 4
					pars.push(NULL)
				pars.push(term:param) # what if this is also a placeholder?
				# console.log term:param

			"{tref}.css$var({AST.cary(pars,as: 'js').join(',')})"

		elif key.indexOf("aria-") == 0 or (@tag and @tag.isSVG) or key == 'for' or TAG_GLOBAL_ATTRIBUTES[key]
			if ns
				"{tref}.setns$('{ns}','{key}',{val})"
			else
				"{tref}.set$('{key}',{val})"

		elif key.indexOf("data-") == 0
			# "set('{key}',{val})"
			"{tref}.setAttribute('{key}',{val})"
			# "dataset.{key.slice(5)}{op}{val}"
		elif ns
			"{tref}.setns$('{ns}','{key}',{val})"
		else
			OP('.',LIT(tref),key).c + "{op}{val}"

export class TagStyleAttr < TagAttr

export class TagAttrValue < TagPart

	def isPrimitive
		value.isPrimitive

	def value
		name

	def js
		value.c

	def toRaw
		if value isa Str
			return value.raw
		return null

export class TagHandlerSpecialArg < ValueNode
	def isPrimitive
		yes

	def c
		"'~${value}'"

export class TagModifiers < ListNode

	def isStatic
		# the check should be for the params, no?
		@nodes.every(do |item|
			let val = item isa Parens ? item.value : item
			val isa Func ? !val.nonlocals : val.isPrimitive
		)

	def visit
		var keys = {FUNC: 0}
		for node,i in nodes
			let key = String(node.name)

			if keys[key]
				node.name = key + '~' + (keys[key]++)
			else
				keys[key] = 1
		self

	def extractDynamics
		return @dynamics if @dynamics
		@dynamics = []

		for part,i in nodes when part isa TagModifier

			for param,k in part.params

				if !param.isPrimitive
					let ref = TagDynamicArg.new(param).set(
						key: KEY(part.name),
						index: k
					)
					part.params.swap(param,LIT('null'))
					@dynamics.push(ref)
		return @dynamics

	def c
		if STACK.tsc
			return '[' + nodes.map(do $1.c()).join(',') + ']'

		let obj = Obj.new([])
		for part in nodes
			let val = part.params ? Arr.new(part.params) : LIT('true')
			obj.add(KEY(part.name),val)
		return obj.c
		# Arr.new(@nodes).c

export class TagModifier < TagPart	
	prop params

	def load value
		if value isa IdentifierExpression
			return value.@single
		return value

	def isPrimitive
		!params or params.every do |param| param.isPrimitive

	def visit
		if @name isa TagHandlerCallback
			@name.traverse
			@name = @name.value

		if @name isa Func # not to be isolated for tsc
			let evparam = @name.params.at(0,yes,'e')
			let stateparam = @name.params.at(1,yes,'$')
			@name.traverse

		if @name isa IsolatedFunc
			@value = @name

			let root = STACK.parents(TagLike)[0]
			if root and USE_SAFE_RENDER_SELF and !(root:isSelf and root.isSelf)
				root.set(memoSelf: yes)
				# should only be needed _if_ self is referenced? But this is a micro optimization
				let op = @value.@scope.@context = OP('.',root.parentCache,STR('this'))

			@name = STR('$_')
			@params = ListNode.new([@value].concat(@value.leaks or []))

		# @value.traverse if @value
		# console.log "visit modifier {@name}"

		@params.traverse if @params

		return self

	def js
		if STACK.tsc
			if @name isa Func
				return "(" + @name.c + ")(e,\{\})"
			# let key =
			let key = quoted.slice(1,-1).split('-')
			let inv = no

			if key[0][0] == '!'
				inv = yes
				key[0] = key[0].slice(1)

			# if key[0] == 'options'
			# 	key[0] = '___setup'

			let path = key[0]

			if key:length > 1
				if path == 'emit' or path == 'flag' or path == 'css'
					path = "{path}-name"
				else
					path = key.join('-')

			path = helpers.toValidIdentifier('α' + path)
			let parjs = params ? params.c : ''

			if params and parjs == ''
				if path == 'αoptions'
					parjs = M('',MLOC(@handlerName.endLoc + 1))
				else
					parjs = M('',MLOC(@name.endLoc + 1))

			let call = "{M(path,@name)}({parjs})"

			if !params or params.count == 0
				call = M(call,@name)

			if inv
				let loc = MLOC(@name.startLoc - 1,@name.startLoc)
				return M("e.{call}===true",loc)

			# return "e.MODIFIERS.{call}"
			return "e.{call}"

		if params and params.count > 0
			"[{quoted},{params.c}]"
		elif params
			"[{quoted}]"
		else
			quoted

export class TagData < TagPart

	def value
		name

	def isStatic
		!value or value.isPrimitive

	def isSpecial
		true

	def isProxy
		proxyParts isa Array

	def proxyParts
		var val = value

		if val isa ArgList
			val = val.values[0]

		if val isa Parens
			val = val.value

		if val isa VarOrAccess
			val = val.@variable or val.value

		if val isa Access
			let left = val.left
			let right = val.right isa Index ? val.right.value : val.right

			if val isa IvarAccess
				left ||= val.scope__.context

			return [left,right]
		return val

	def js
		var val = value

		if val isa ArgList
			val = val.values[0]

		if val isa Parens
			val = val.value

		if val isa VarOrAccess
			val = val.@variable or val.value

		if val isa Access
			let left = val.left
			let right = val.right isa Index ? val.right.value : val.right

			if val isa IvarAccess
				left ||= val.scope__.context

			let pars = [left.c,right.c]

			if right isa Identifier
				pars[1] = "'" + pars[1] + "'"

			"bind$('data',[{pars.join(',')}])"
		else
			"data=({val.c})"

export class TagDynamicArg < ValueNode
	def c
		value.c

export class TagHandler < TagPart

	prop params watch: yes

	def paramsDidSet params
		@chain.push(@last = TagModifier.new('options'))
		@last.@handlerName = @name
		@last.params = params

	def add item, type, start, end
		if type == TagHandlerCallback
			item = item.first if item isa ArgList
			item = TagHandlerCallback.new(item)

		super(item,type)

	def visit
		super
		STACK.use('events')
		# Replace with something better for debugging
		if @name and CUSTOM_EVENTS[String(@name)] and STACK.isWeb
			STACK.use(CUSTOM_EVENTS[String(@name)])

	def isStatic
		let valStatic = !value or value.isPrimitive or (value isa Func and !value.nonlocals)
		# check modifiers directly?
		valStatic and @chain.every(do |item|
			let val = item isa Parens ? item.value : item
			val isa Func ? !val.nonlocals : val.isPrimitive
		)

	def modsIdentifier
		null

	def js o
		if STACK.tsc
			let out = "{tagRef}.addEventListener({quoted},(e)=>" + '{\n'
			for mod in modifiers
				out += mod.c + ';\n'
			out += '})'
			return out

		if @standalone
			let up = @tag
			let iref = "{up.cvar}[{osym}]"
			let mods = modifiers
			let specials = mods.extractDynamics()
			let visit = no
			let out = []
			let add = do |val| out.push(val)
			let hvar = up.hvar

			add "{up.hvar} = {iref} || ({iref}={mods.c(o)})"
			for special in specials
				let k = special.option(:key)
				let i = special.option(:index)
				let path = "{OP('.',hvar,k).c}[{i}]"
				if k == 'options'
					visit = yes
					add "({vvar}={special.c(o)},{vvar}==={path} || ({path}={vvar},{dvar}|={F.DIFF_MODIFIERS}|{F.DIFF_INLINE}))"
				else
					add "{path}={special.c(o)}"

			add "{up.bvar} || {up.ref}.on$({quoted},{hvar.c},{scope__.context.c})"
			if visit
				add "{up.dvar}&{F.DIFF_INLINE} && ({up.dvar}^={F.DIFF_INLINE},{hvar}[{gsym('#visit')}]?.())"

			return "(" + out.join(",\n") + ")"

		return "{tagRef}.on$({quoted},{modifiers.c},{scope__.context.c})"

	# swallow might be better name
	def consume node
		if node isa TagLike
			@tag = node
			@standalone = yes
		return self			
		# return node.register(self)

export class TagHandlerCallback < ValueNode

	def visit
		let val = value

		if val isa Parens
			val = val.value

		if val isa Func
			# TODO Warn / error if func has arguments
			val = val.body

		# If the value is just a variable we can add it directly?

		if val isa Access or val isa VarOrAccess
			# Need to potentially cache the self value here.
			let target = val
			val = CALL(val,[LIT('e')])
			val.@args.@startLoc = target.endLoc
			val.@args.@endLoc = target.endLoc


		# TODO don't generate this until visiting the taghandler

		# if this is a plain access it should be enough to set a reference
		# to the function once?

		value = (STACK.tsc ? Func : IsolatedFunc).new([],[val],null,{})

		if value isa IsolatedFunc
			yes

		if value isa Func
			let evparam = value.params.at(0,yes,'e')
			let stateparam = value.params.at(1,yes,'$$')

		value.traverse
		return

export class TagBody < ListNode

	def add item, o
		if item isa InterpolatedString
			item = item.toArray
			if item:length == 1
				item = TagTextContent.new(item[0])

		super(item,o)

	def consume node
		if node isa TagLike
			@nodes = @nodes.map do |child|
				if !(child isa Meta) # and !(child isa Assign)
					child.consume(node)
				else
					child
			return self
		super

class TagLike < Node

	def initialize o = {}
		@options = o
		@flags = 0
		@tagvars = {}
		setup(o)
		self

	def isIndexableInLoop
		no

	def sourceId
		@sourceId ||= STACK.sourceId + '-' + tid

	def body
		@body || @options:body

	def value
		@options:value

	def isReactive
		yes

	def isDetached
		option(:detached)

	def isSVG
		@isSVG ?= (@parent ? @parent.isSVG : false)

	def parentTag
		let el = @parent
		while el and !(el isa Tag)
			el = el.@parent
		return el

	def tagLikeParents
		let parents = []
		let el = @parent
		while el isa TagLike
			parents.push(el)
			el = el.parent

		return parents

	def setup
		@traversed = no
		@consumed = []
		self

	def osym ns = ''
		STACK.getSymbol(oid + ns,InternalPrefixes.SYM + (tagvarprefix or '') + ns)

	def root
		@parent ? @parent.root : self

	def register node

		if node isa If or node isa Switch
			flag(F.TAG_HAS_BRANCHES)
			node = TagSwitchFragment.new(body: node)
		elif node isa Loop
			flag(F.TAG_HAS_LOOPS)
			node = TagLoopFragment.new(body: node.body, value: node)
		elif node isa Tag
			flag(F.TAG_HAS_DYNAMIC_CHILDREN) if node.isSlot
		elif node isa Op
			node = node.opToIfTree

			if node isa If
				flag(F.TAG_HAS_BRANCHES)
				node = TagSwitchFragment.new(body: node)
			else
				flag(F.TAG_HAS_DYNAMIC_CHILDREN)
				node = TagContent.new(value: node)

		elif node isa StyleRuleSet
			yes
		else
			flag(F.TAG_HAS_DYNAMIC_CHILDREN) unless node isa Str
			node = TagContent.new(value: node)

		@consumed.push(node) # why consume if node isa String?
		node.@consumedBy = self
		node.@parent = self
		return node

	def flag key
		@flags |= key

	def type
		"frag"

	def unflag key
		@flags = @flags & ~key

	def hasFlag key
		@flags & key

	def isAbstract
		true

	def isOnlyChild
		isFirstChild && isLastChild

	def isFirstChild
		hasFlag(F.TAG_FIRST_CHILD)

	def isLastChild
		hasFlag(F.TAG_LAST_CHILD)

	def isIndexed
		option(:indexed)

	def isComponent
		@kind == 'component'

	def isSelf
		type isa Self or type isa This

	def isShadowRoot
		@tagName and @tagName == 'shadow-root'

	def isSlot
		@kind == 'slot'

	def isFragment
		@kind == 'fragment'

	def isMemoized
		!option(:unmemoized)

	def hasLoops
		hasFlag(F.TAG_HAS_LOOPS)

	def hasBranches
		hasFlag(F.TAG_HAS_BRANCHES)

	def hasDynamicChildren
		hasFlag(F.TAG_HAS_DYNAMIC_CHILDREN)

	def hasDynamicFlags
		hasFlag(F.TAG_HAS_DYNAMIC_FLAGS)

	def hasNonTagChildren
		hasLoops or hasBranches or hasDynamicChildren

	def hasDynamicDescendants
		return true if hasNonTagChildren
		for el in @consumed
			if el isa Tag
				return true if el.hasDynamicDescendants
		return false

	def hasChildren
		@consumed:length > 0

	def tagvar name
		name = InternalPrefixes[name] or name
		@tagvars[name] ||= scope__.closure.temporary(null,{nodecl: STACK.tsc, reuse: no, alias: "{name}{tagvarprefix}"},"{name}{tagvarprefix}")

	def tagvarprefix
		""

	def level
		@level

	def parent
		@parent ||= option(:parent)

	def fragment
		@fragment || parent

	def tvar
		@tvar or tagvar('T')

	def parentRef
		@parentRef ||= (parent ? parent.ref : "{parentCache}._")

	def parentCache
		@parentCache ||= (parent ? parent.cvar : (isMemoized ? scope__.closure.tagCache : scope__.closure.tagTempCache))

	def renderContextFn
		"{parentCache}[{gsym('#getRenderContext')}]"

	def dynamicContextFn
		"{parentCache}[{gsym('#getDynamicContext')}]"

	# built variable
	def bvar
		@bvar or (@parent ? @parent.bvar : tagvar('B'))

	# cache variable
	def cvar
		@cvar or (@parent ? @parent.cvar :  tagvar('C'))

	def owncvar
		tagvar('C')

	def vvar do tagvar('V') # value variable
	def hvar do tagvar('H') # handler variable
	def kvar do tagvar('K') # key variable

	# for tracking specific changes -- included in end
	# shuold maybe link it with built
	def dvar do tagvar('D') # value variable

	def ref
		@ref || (@cachedRef = "{parent ? parent.cvar : ''}[{osym}]")

	def visit stack
		var o = @options
		var scope = @tagScope = scope__

		if up isa Op
			set(detached: yes)

		let prevTag = @parent = stack.@tag
		@level = (@parent && @parent.@level or 0) + 1
		stack.@tag = null

		for part in @attributes
			part.traverse

		stack.@tag = self

		if o:key
			o:key.traverse

		visitBeforeBody(stack)

		if body
			body.traverse

		visitAfterBody(stack)

		stack.@tag = @parent

		unless @parent
			@level = 0
			consumeChildren
			visitAfterConsumed

		self

	def visitBeforeBody
		self

	def visitAfterBody
		self

	def consumeChildren
		return if @consumed:length
		body && body.consume(self)
		let first = @consumed[0]
		let last = @consumed[@consumed:length - 1]

		# too many edgecases to really utilize this
		if !isAbstract
			first.flag(F.TAG_FIRST_CHILD) if first isa TagLike
			last.flag(F.TAG_LAST_CHILD) if last isa TagLike

		for item in @consumed when item isa TagLike
			item.@consumedBy = self
			item.@parent = self
			item.@level = (@level + 1)
			item.visitAfterConsumed
			item.consumeChildren

		visitAfterConsumedChildren
		self

	def visitAfterConsumedChildren
		self

	def visitAfterConsumed
		self

	def consume node
		if node isa TagLike
			return node.register(self)

		if node isa Variable
			option('assignToVar',node)
			return self

		if node isa Assign
			return OP(node.op,node.left,self)
		elif node isa VarDeclaration
			return OP('=',node.left,self)
		elif node isa Op
			return OP(node.op,node.left,self)
		elif node isa Return
			# console.log "return is consuming tag"
			option('return',yes)
			return self
		return self

export class TagTextContent < ValueNode

export class TagContent < TagLike

	def vvar
		parent.vvar

	def bvar
		parent.bvar # is this not the parent bvar?

	def ref
		fragment.tvar

	def key
		# @key ||= "{parent.cvar}.{oid}"
		@key ||= "{parent.cvar}[{osym}]"

	def isStatic
		value isa Str or value isa Num

	def js
		let value = self.value
		let parts = []
		let isText = (value isa Str or value isa Num or value isa TagTextContent)
		let isStatic = self.isStatic

		if STACK.tsc
			return value.c(o)

		if parent isa TagSwitchFragment or (@tvar and parent isa Tag and (parent.isSlot or isDetached))
			# what if it is a call?
			parts.push("{@tvar}={value.c(o)}")

			if value isa Call or value isa BangCall
				# mark parent to reset imba.ctx at the end
				let k = "{parent.cvar}[{osym('$')}]"
				parts.unshift("{runtime:renderContext}.context=({k} || ({k}=\{_:{fragment.tvar}\}))")
				parts.push("{runtime:renderContext}.context=null")

		elif isOnlyChild and (value isa Str or value isa Num)
			return "{bvar} || {ref}.text$({value.c(o)})"
		elif isStatic
			return "{bvar} || {ref}{domCall('insert')}({value.c(o)})"
		elif value isa TagTextContent and isOnlyChild and !(parent isa TagSwitchFragment)
			return "({vvar}={value.c(o)},{vvar}==={key} || {ref}.text$(String({key}={vvar})))"
		else
			parts.push("{vvar}={value.c(o)}")

			let inskey = "{parent.cvar}[{osym(:i)}]"
			# TODO rework to only introduce context with <( dynamic )> syntax (expecting tags)
			if value isa Call or value isa BangCall
				# let k = "{parent.cvar}[{osym.c}]"
				let k = "{parent.cvar}[{osym('$')}]"
				# mark parent to reset imba.ctx at the end
				parts.unshift("{runtime:renderContext}.context=({k} || ({k}=\{_:{fragment.tvar}\}))")
				parts.push("{runtime:renderContext}.context=null")

			if value isa TagTextContent
				parts.push("({vvar}==={key}&&{bvar}) || ({inskey} = {ref}{domCall('insert')}(String({key}={vvar}),{@flags},{inskey}))")
			else
				parts.push("({vvar}==={key}&&{bvar}) || ({inskey} = {ref}{domCall('insert')}({key}={vvar},{@flags},{inskey}))")

		return "(" + parts.join(',') + ')'

export class TagFragment < TagLike

export class TagSwitchFragment < TagLike

	def setup
		super
		@branches = []
		@inserts = []
		@styles = []

	def getInsertVar index
		@inserts[index] ||= tagvar('τ' + index + 'if') # tagvar(self.oid + '$' + index)

	def getStyleVar index
		@styles[index] ||= tagvar('τ' + index + 'css') # tagvar(self.oid + '$' + index)

	def tvar
		fragment.tvar

	def register node
		let res = super

		if @branches
			let curr = @branches[@branches:length - 1]
			curr && curr.push(res)
		return res

	def visitAfterConsumedChildren

		unless @parent isa TagSwitchFragment
			let max = self.assignChildIndices(0,0,self)
		return self

	def assignChildIndices start,stylestart,root
		# use different counters for flags?
		let nr = start
		let max = start

		let stylenr = stylestart
		let stylemax = stylestart

		for branch,i in @branches
			nr = start
			# stylenr = stylestart
			for item in branch
				if item isa TagSwitchFragment
					let res = item.assignChildIndices(nr,stylenr,root)
					nr = res[0]
					stylenr = res[1]
				elif item isa StyleRuleSet
					item.@tvar = root.getStyleVar(stylenr)
					item.@tvar.@stylerule = item
					stylenr++
				else
					if !STACK.tsc
						item.@tvar = root.getInsertVar(nr)
					item.set(detached: yes)
					nr++

			if nr > max
				max = nr

			if stylenr > stylemax
				stylemax = stylenr

		return [max,stylemax]

	def js o
		var parts = []

		var top = ''
		let vars = @inserts.concat(@styles)
		if vars.len
			top = vars.join(' = ') + ' = null'

		let wasInline = o:inline
		if body.isExpression
			o:inline = yes
		var out = body.c(o)
		o:inline = wasInline

		return out if STACK.tsc

		parts.push(top) if top
		parts.push(out)

		for item,i in @inserts
			let key = "{cvar}[{osym(i)}]"
			parts.push("({key} = {tvar}{domCall('insert')}({item},0,{key}))")

		for item,i in @styles
			let flag = item.@stylerule.@name
			parts.push("{tvar}.flags.toggle('{flag}',!!{item})")

		if o:inline
			return parts.join(',')
		else
			return parts.join(';\n')

export class TagLoopFragment < TagLike

	def isKeyed
		option(:keyed) or hasFlag(F.TAG_HAS_BRANCHES)

	def isIndexableInLoop
		yes

	def consumeChildren
		super

		# determine if the order of elements will ever change inside loop
		if hasFlag(F.TAG_HAS_BRANCHES)
			set(keyed: yes)
		elif @consumed.every(do $1 isa TagLike and $1.isIndexableInLoop)
			set(indexed: yes)
		else
			set(keyed: yes)

	def cvar
		@cvar || tagvar('C')

	def js o

		if stack.isExpression
			let fn = CALL(FN([],[self],stack.scope),[])
			# the inner tag loop has to get the opdated scope as well
			# fn.traverse
			return fn.c

		if STACK.tsc
			return "{tvar} = new DocumentFragment;\n{value.c(o)}"

		if parent isa TagLoopFragment and parent.isKeyed
			set(detached: yes)

		if parent isa TagSwitchFragment
			set(detached: yes)

		if parent and !@consumedBy
			set(detached: yes)

		# if @tag and @childTags
		let iref = option(:indexed) ? (runtime:createIndexedList) : (runtime:createKeyedList)		
		# LIT('imba.createIndexedFragment') : LIT('imba.createKeyedFragment')
		# should know how many inner slots this fragment has?
		let cache = parent.cvar
		let parentRef = isDetached ? LIT('null') : fragment.tvar

		let out = ""
		let refpath

		if parent isa TagLoopFragment
			if parent.isKeyed
				option(:key,OP('+',LIT("'{oid}$'"),parent.kvar))
				out += "{hvar}={option(:key).c};\n"
				refpath = @ref = "{parent.cvar}[{hvar}]"
			else
				refpath = @ref = "{parent.cvar}[{parent.kvar}]"
		else
			refpath = "{cache}[{osym}]"

		out += "({tvar} = {refpath}) || ({refpath}={tvar}={iref}({@flags},{parentRef}));\n"
		@ref = "{tvar}"
		if isDetached
			out += "{tvar}[{gsym('##up')}] = {fragment.tvar};\n"
		out += "{kvar} = 0;\n"
		out += "{cvar}={tvar}.$;\n"
		out += value.c(o)
		out += ";{tvar}{domCall 'end'}({kvar});"

		if parent isa TagLoopFragment
			if parent.isKeyed
				out += "{parent.ref}.push({tvar},{parent.kvar}++,{hvar});"
			elif parent.isIndexed
				out += "{parent.kvar}++;"

		return out

export class TagIndexedFragment < TagLike

export class TagKeyedFragment < TagLike

export class TagSlotProxy < TagLike

	def ref
		tvar

	def tagvarprefix
		oid+'S'

class TagNodeClass
export class Tag < TagLike

	prop attrmap

	def setup
		super
		@attributes = @options:attributes or []
		@attrmap = {}
		@classNames = []
		@className = null

	def isAbstract
		isSlot or isFragment

	def attrs
		@attributes

	def cssns
		@cssns ||= "{sourceId}".replace('-','_')

	def cssid
		@cssid ||= "{sourceId}".replace('_','-')

	def tagvarprefix
		return isSelf ? 'SELF' : 'T'
		return @tagvarprefix ||= (type and type:toVarPrefix ? type.toVarPrefix : (isSelf ? 'self' : 'tag'))
		return ''

	def isStatementLike
		option(:iife)

	def isIndexableInLoop
		!option(:key) and !isDynamicType

	def traverse
		return self if @traversed
		tid
		@tagDeclaration = STACK.up(TagDeclaration)
		let close = @options:close
		let body = @options:body or []
		let returns = self

		if close and close.@value == '/>' and body.len
			returns = [self].concat(body.@nodes)
			@options:body = ArgList.new([])

		super

		return returns

	def visitBeforeBody stack
		oid
		tid
		let type = @options:type
		type && type.traverse

		if STACK.hmr
			self.cssid

		if isSelf or (tagName.indexOf('-') >= 0) or isDynamicType or (type && type.isComponent)
			@options:custom = yes
			@kind = 'component'
		else
			@kind = 'element'

		if attrs:length == 0 && !@options:type
			@options:type = 'fragment'

		let tagName = self.tagName

		if tagName == 'slot'
			@kind = 'slot'

		elif tagName == 'fragment'
			@kind = 'fragment'

		if tagName == 'shadow-root'
			@kind = 'shadow-root'

		if isSelf
			let decl = stack.up(TagDeclaration)
			decl.set(self: self,sourceId: sourceId) if decl

		@tagName = tagName

		@dynamics = []

		let i = 0
		while i < @attributes:length
			let item = @attributes[i++]
			if item isa TagFlag and item.name isa StyleRuleSet				
				if item.name.placeholders:length
					for ph in item.name.placeholders
						let setter = TagStyleAttr.new(ph.name)
						setter.@tag = self
						setter.value = ph.runtimeValue
						setter.set(
							propname: ph.@propname
							unit: ph.option('unit')
							styleterm: ph
						)
						@attributes.splice(i++,0,setter)
						setter.traverse

		@attributes = @attributes.filter do |item|

			if item isa TagFlag and item.isStatic
				@classNames.push(item)
				return false

			unless STACK.tsc
				if item == @attrmap:$key
					item.warn("$key= is deprecated, use key=", loc: item.@name)
					self.set(key: item.value)
					return false

				if item == @attrmap:key
					self.set(key: item.value)
					return false

			if !item.isStatic
				@dynamics.push(item)

			return true

		if @parent
			if @attrmap:route or isDynamicType or isSlot
				@parent.set(shouldEnd: yes, ownCache: yes)

		if isSlot
			# @tvar = tagvar('t'+oid)
			let name = @attrmap:name ? @attrmap:name.value : '__'
			name = name.raw if name isa Str
			set(name: name)
			@attributes = []

		@scope = TagBodyScope.new(self)
		@scope.visit

		super

	def register node
		node = super(node)

		if node isa TagLike and (isComponent and !isSelf)
			let slotKey = node isa Tag ? node.@attrmap:slot : null
			let name = '__'
			if slotKey
				if slotKey.value isa Str
					name = slotKey.value.raw
			# let name = slotKey ? slotKey.value .toRaw : '__'
			let slot = getSlot(name)
			node.@fragment = slot
		return node

	def visitAfterBody stack
		self

	def visitAfterConsumed
		if isSVG
			@kind = 'svg'

		if @options:reference
			let method = stack.up(MethodDeclaration)
			let tagdef = stack.up(TagDeclaration)
			let err

			if @options:key
				err = "Named element cannot be keyed at the same time"

			if tagdef and method and String(method.name) == 'render'
				for el in tagLikeParents
					if el isa TagLoopFragment
						err = "Named tags not allowed inside loops"
					if el isa Tag and el.isDynamicType
						err = "Named tags not allowed inside dynamic parent"

				unless err
					tagdef.addElementReference(@options:reference,self)
			else
				err = "Named tags are only allowed inside render method"

			if err
				# FIXME should actually classify as error
				warn(err, loc: @options:reference)

		# FIXME Slots are not allowed inside loops

		self

	def visitAfterConsumedChildren
		if isSlot and @consumed:length > 1
			set(markWhenBuilt: yes, reactive: yes)
		return

	def hasBlockScopedVariables
		Object.keys(@scope.varmap):length > 0

	def getSlot name
		@slots ||= {}
		@slots[name] ||= TagSlotProxy.new(parent: self, name: name)

	def addPart part, type, tok
		let attrs = @attributes
		let curr = attrs.CURRENT
		let next = curr

		if type == TagId
			set(id: part)

		if type == TagArgList
			if attrs:length == 0

				let typ = option(:type)
				typ = null if typ.@token == 'div'
				set(dynamic: yes)
				let op = part.nodes[0]

				if typ
					op = CALL(typ.toFunctionalType,part.nodes)
				set(type: op, functional: op)
				return self

		if type == TagSep
			next = null

		elif type == TagAttrValue
			if part isa Parens
				part = part.value

			if curr isa TagFlag
				curr.condition = part
				flag(F.TAG_HAS_DYNAMIC_FLAGS)
				curr.set(op: tok)
			elif curr isa TagHandler

				if part
					# if part isa Access or part isa VarOrAccess
					#	# let e = Token.new('IDENTIFIER','e')
					#	part = CALL(part,[LIT('e')])
					# console.log 'is stack tsc?'
					# TODO don't generate this until visiting the taghandler
					# part = (STACK.tsc ? Func : IsolatedFunc).new([],[part],null,{})
					curr.add(TagHandlerCallback.new(part),type)

			elif curr
				curr.value = part
				curr.set(op: tok)

		elif curr isa TagHandler
			if part isa IdentifierExpression and part.single and !part.isPrimitive
				# console.log 'is stack tsc?'
				part = (STACK.tsc ? Func : IsolatedFunc).new([],[part.single],null,{})

			curr.add(part,type)
		elif curr isa TagAttr
			curr.add(part,type)

		else
			if type == TagFlag and part isa IdentifierExpression and !part.isPrimitive
				flag(F.TAG_HAS_DYNAMIC_FLAGS)

			if part isa type
				part.@tag = self
			else
				part = type.new(part,self)

			attrs.push(next = part)

			if next isa TagAttr and next.name.isPrimitive
				let name = String(next.name.toRaw)

				if name.match(/^bind(?=\:|$)/) and isFunctional
					next.@name.error "bind not supported for functional fragments"
				if name == 'bind'

					(next.@name.@single or next.@name).@value = 'bind:data'
					name = 'bind:data'
				@attrmap[name] = next

		if next != curr
			attrs.CURRENT = next
		self

	def type
		@options:type || (@attributes:length == 0 ? :fragment : :div)

	def tagName
		@tagName || String(@options:type)

	def isDynamicType
		type isa ExpressionNode or @options:dynamic
	
	def hasDynamicTagName
		type isa ExpressionNode

	def isFunctional
		!!@options:functional

	def isSVG
		@isSVG ?= ((type isa TagTypeIdentifier and type.isSVG) or (@parent and @parent.isSVG and !isDynamicType))

	def isAsset
		@isAsset or no

	def create_
		if isFragment or isSlot
			runtime:createLiveFragment
			# LIT('imba.createLiveFragment')
		elif isAsset
			runtime:createAssetElement
		
		elif isDynamicType
			runtime:createDynamic

		elif isSVG
			runtime:createSVGElement
			# LIT('imba.createSVGElement')
		elif isComponent
			runtime:createComponent
		else
			runtime:createElement

	def isReactive
		option(:reactive) or (@parent ? @parent.isReactive : !(scope__ isa RootScope))

	def isDetached
		option(:detached)

	def hasDynamicParts
		if @dynamics:length == 0 and !hasDynamicFlags and !(type isa ExpressionNode)
			let nodes = body ? body.values : []
			if nodes.every(|v| v isa Str or (v isa Tag and !v.isDynamicType))
				if !hasNonTagChildren and !isSlot and !option(:dynamic)
					hasDynamicParts = no
		return yes

	def js o
		var stack = STACK
		var tsc = STACK.tsc
		var isExpression = stack.isExpression

		var head = []
		var out = []
		var foot = []

		var add = do |val|
			if val isa Variable
				val = val.toString
			out.push(val)

		var parent = self.parent
		var fragment = self.fragment
		var component = @tagDeclaration
		let oscope = @tagDeclaration ? @tagDeclaration.scope : null

		let typ = isSelf ? "self" : (isFragment ? "'fragment'" : (type:isClass and type.isClass ? type.toTypeArgument : "'" + type.@value + "'"))

		if type.@value == 'global' or type.@value == 'teleport'
			# console.warn "global will be deprecated in favor of teleport in a future version. Please use teleport instead" unless type.@value == 'teleport'
			typ = "'i-{type.@value}'"
			STACK.use('dom_teleport')

		if parent and !@consumedBy
			set(detached: yes)

		var parentIsInlined = o:inline

		var isSVG = self.isSVG
		var isReactive = self.isReactive

		var canInline = no
		var hasDynamicParts = yes
		var useRoutes = @attrmap:route or @attrmap:routeTo or @attrmap['route-to']
		var shouldEnd = isComponent or useRoutes or option(:shouldEnd)

		if useRoutes
			stack.use('router')

		var dynamicKey = null
		var ownCache = option(:ownCache) or no

		if @asset
			typ = @assetRef.c
			# typ = "'{@assetName}'"

		var slotPath = ""

		if isSlot
			if root.isSelf
				slotPath = OP('.',OP(".",root.tvar,STR("__slots")),STR(option(:name))).c
			else
				let fn = OP(".",root.tvar,gsym('#registerFunctionalSlot')).c
				slotPath = "{fn}({STR(option(:name)).c})"

		if tsc
			let up = STACK.parent
			let safe = up isa Block or up isa Tag

			if !safe
				option(:iife,true)
			# if not we need to wrap the whole thing in an iife
			# let up = STACK.
			if type isa TagTypeIdentifier and !isSelf
				if type.isAsset
					add "var {tvar} = new {M("SVGSVGElement",type)}"
				elif type.isClass
					add M("var {tvar} = new {M(type.toClassName,type)};{tvar}",type)
				else
					tvar.@datatype = MappedString.new(type.toClassName,type)
					add M("var {tvar} = new {M(type.toClassName,type)};{tvar}",type)

			elif isSelf
				tvar.@datatype = 'this'
				add "var {tvar} = {type.c}"
			elif isDynamicType
				if @options:dynamic
					add "var {tvar} = {type.c};{tvar}"
				else
					add "var {tvar} = new {M('Γany',type)}"
			else
				add "var {tvar} = new {M('HTMLElement',type)}"

			for item in @attributes
				@ref = tvar
				if item isa TagAttr or item isa TagHandler or item isa TagFlag
					add item.c(o) # M("{tvar}.{item.c(o)}",item)
					# add M("{tvar}.{item.c(o)}",item)
				self
			let nodes = body ? body.values : []
			for item in nodes
				add item.c
			
			if !safe
				add "return {tvar}"
				let sep = o:inline or isExpression ? ',' : ';\n'
				sep = ';\n'
				return '(()=>{' + out.join(sep) + '})()'
			elif false and (o:inline or isExpression)
				# o:inline = wasInline
				add option(:return) ? "return {tvar}" : "{tvar}"
				let js = '(' + out.join(',\n') + ')'
				return js
			else
				if option(:return)
					add "return {tvar}"

				let js = out.join(";\n")
				if hasBlockScopedVariables
					js = '{' + js + '}'
				return js

		# whether this tag should set a variable indicating
		# whether this was built now or not
		# basically whether we need a reference at all?
		var markWhenBuilt = shouldEnd or hasDynamicFlags or attrs:length or option(:markWhenBuilt) or isDetached or isDynamicType or !!option(:key)
		# when it has any attributes? - but not text or

		var inCondition = parent && parent.option(:condition)

		if isDynamicType
			ownCache = yes
			if isMemoized
				typ = "{owncvar}.value"
			else
				typ = type.c
			# @cref = "{parentCache}[{osym('$2')}]"

		# add unique flag to this element if it has inline styles or
		# we're compiling for hmr.

		if @cssid
			@classNames.unshift(cssid)

		for closure in STACK.closures
			if closure.@cssns and (!isSelf or closure != oscope)
				@classNames.push(closure.@cssns)

		for par in tagLikeParents
			if par.@cssns
				@classNames.push(par.@cssns)

		if component and !isSelf
			if let cname = component.cssref(option(:reference) ? null : scope__)
				let orig = component.@cssns
				# TOOD store in part of static classNames instead. Signal that these are static
				if @classNames.indexOf(orig) >= 0
					@classNames.splice(@classNames.indexOf(orig),1)

				if isDynamicType and true
					@styleName = cname
				else
					@classNames.push cname

		if option(:reference)
			if oscope
				let name = String(option(:reference)).slice(1)
				@classNames.push("${name}") # just add the actual ref right?

		if option(:key)
			set(detached: yes)

		if @classNames:length
			let names = []
			let dynamic = no
			for cls,i in @classNames
				if cls isa TagFlag
					if cls.name isa MixinIdentifier
						names.push(cls.name.toRaw)
						# dynamic = yes
					else
						names.push(cls.rawClassName)
				elif cls isa Node
					dynamic = yes
					names.push('${' + cls.c + '}')
				else
					names.push(cls)

			names = names.filter do |item,i| names.indexOf(item) == i
			let q = dynamic ? '`' : "'"
			@className = q + names.join(' ') + q

		var params = [
			typ,
			(fragment and !option(:detached) ? fragment.tvar : 'null'),
			@className or 'null',
			'null',
			(@styleName ? @styleName.c() : 'null')
		]

		# if @asset
		#	params[0] = OP('.',@asset:ref,typ).c
		#	# Arr.new([typ,@asset:ref]).c

		var nodes = body ? body.values : []

		if nodes:length == 1 and nodes[0] isa TagContent and nodes[0].isStatic and !isSelf and !isSlot
			params[3] = nodes[0].value.c
			nodes = []

		# checking to see if a node is static enough to be inserted directly into the dom without
		# any references.
		if @dynamics:length == 0 and !hasDynamicFlags and !dynamicKey and !isDynamicType and !option(:slotted)
			if nodes.every(|v| v isa Str or (v isa Tag and !v.isDynamicType and !v.option(:key)))
				if !shouldEnd and !hasNonTagChildren and !isSlot and !option(:dynamic) and !option(:reference)
					hasDynamicParts = no
					if parent isa Tag and !(up isa Op)
						# console.log "CAN INLINE {tagName} {parent} {up}"
						canInline = yes

		if isFragment or isSlot
			params = [@flags].concat(params.slice(1,2)) # .slice(1,3)

		if isSlot
			# the slot is not supposed to be inserted immediately
			params[1] = 'null'

		var ctor = M("{create_}({params.join(',')})",type)

		if option(:reference)
			# TODO need to ensure that the name is resolved outside of render
			# what if it is on root?
			let par = params[1]
			params[1] = 'null'
			ctor = M("{create_}({params.join(',')})",type)
			set(ctor: ctor)
			# ctor = OP('=',OP('.',scope__.context,option(:reference)),LIT(ctor)).c()
			ctor = OP('.',scope__.context,option(:reference)).c()
			ctor = "({tvar}={ctor},{tvar}[{gsym('##up')}]={par},{tvar})"

			let decl = option(:tagdeclbody)

			if decl and !STACK.tsc
				let head = decl.@head ||= []
				let ref = helpers.toValidIdentifier(option(:reference).c)
				let gen = option(:ctor)
				# let sym = STACK.getSymbol
				# let body = "return this[{sym}] || (this[{sym}] = {gen})"
				let body = "let el={gen};\n\treturn (Object.defineProperty(this,'{ref}',\{value:el\}),el);"
				let getter = "get {ref}()" + '{\n\t' + body + '\n}'
				head.push( getter )

			# ctor = OP('.',scope__.context,option(:reference)).c()
			# should also check if there is already an element like this
			# ctor = CALL(OP('.',LIT(ctor),'ref$'),[STR(option(:reference))])
		else	
			ctor = "{tvar}={ctor}"

		if option(:assign)
			# push it into ctor if it is not a variable assignment
			# otherwise we always want to assign it
			ctor = OP('=',option(:assign),LIT(ctor)).c()

		let deeplyDynamic = hasDynamicDescendants

		# console.log "IS INLINE? {o:inline} {STACK.isExpression} {!!@consumedBy} {!!@parent} {isDetached}"

		if !parent and option(:memoSelf) and !isSelf # scope__.@context # and scope__.@context.@reference
			# what if there is no parent cache here?
			add "{parentCache}.this=this"

		if !@consumedBy
			@ref = "{tvar}"

			

			if isSelf
				add "{tvar}=this"
				add "{tvar}{domCall 'open'}()"
				add "({bvar}={dvar}=1,{tvar}[{osym}] === 1) || ({bvar}={dvar}=0,{tvar}[{osym}]=1)"
				@cvar = tvar

			elif isReactive
				# let scop = scope__.closure
				let k = "{parentCache}[{osym}]"

				if isDynamicType and isMemoized
					if option(:key)
						# what if this is the only one, should be special?
						add "{owncvar}={dynamicContextFn}({osym},{option(:key).c})"
					else
						add "{owncvar}={renderContextFn}({osym})"

					ctor = "{owncvar}.cache({ctor})"
					add "({bvar}={dvar}=1,{tvar}={owncvar}.run({type.c},{hasDynamicTagName ? 1 : 0})) || ({bvar}={dvar}=0,{ctor})"

				elif option(:key)
					add "{cvar}=({k}={k}||new Map())"
					add "({bvar}={dvar}=1,{tvar}={cvar}.get({kvar}={option(:key).c})) || ({bvar}={dvar}=0,{cvar}.set({kvar},{ctor}))"
				else
					if isMemoized
						add "({bvar}={dvar}=1,{tvar}={k}) || ({bvar}={dvar}=0,{tvar}={k}={ctor})"
					else
						add "({bvar}={dvar}=0,{tvar}={ctor})"

				add "{bvar} || ({tvar}[{gsym('##up')}] = {parentRef})" # really?
				add "{bvar} || ({tvar}[{gsym('##register')}]?.({parentCache},{osym}))" # really?

				@cvar = tvar
				@ref = tvar

				if isExpression and !deeplyDynamic
					option(:inline,canInline = yes)
					o:inline = yes
				else
					if isExpression
						option(:iife,yes)

					o:inline = no

			else
				add "({ctor})"
				@cvar = tvar

				if isExpression and !hasDynamicParts
					option(:inline,canInline = yes)
					o:inline = yes
					# console.log 'can inline',tagName
				else
					option(:iife,yes)
					o:inline = no

		else
			# console.log tagName,'re',isReactive,'expr',isExpression,'inline',canInline,'dyn',hasDynamicParts,'oin',o:inline
			# if the parent was inlined but we are too complex
			if o:inline and !canInline
				# what if this is just asking to be inlined because of a ternary?
				option(:iife,yes)
				o:inline = no

			if isShadowRoot
				let key = "{cvar}[{osym}]"
				add "{tvar}={key} || ({key}={fragment.tvar}.attachShadow(\{mode:'open'\}))"

			elif isSlot and !hasChildren
				add("{tvar}={slotPath}")
				unless parent isa TagSwitchFragment
					let key = "{cvar}[{osym}]"
					add("({key} = {fragment.tvar}{domCall('insert')}({tvar},{@flags},{key}))")

			elif isSlot and @consumed:length == 1
				# single child can act as slot?
				# if it is a string we dont really want to insert it at all
				@consumed[0].set(detached: yes, slotted: yes)
				@consumed[0].@tvar = tvar
				@consumed[0].@parent = parent

				ownCache = no
				# add("{tvar}")

			elif parent isa TagLoopFragment
				@bvar = tagvar('B')
				let key = option(:key)

				if option(:key)
					if isDynamicType
						add "{owncvar}={renderContextFn}({option(:key).c})"
						let gets = "{owncvar}.run({type.c})"
						add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{owncvar}.cache({ctor}))"
					else
						let gets = "{parentCache}.get({kvar}={option(:key).c})"
						add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{parentCache}.set({kvar},{ctor}))"

				elif parent.isIndexed
					let memo = "{parentCache}[{parent.kvar}]"
					add "({bvar}={dvar}=1,{tvar}={memo}) || ({bvar}={dvar}=0,{memo}={ctor})"

				elif parent.isKeyed
					if !isDynamicType
						let gets = "({kvar}={renderContextFn}({osym})).get({parent.kvar})"
						add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{kvar}.set({parent.kvar},{ctor}))"
					else
						let gets = "({owncvar}={dynamicContextFn}({type.osym},{parent.kvar})).run({type.c},{hasDynamicTagName ? 1 : 0})" # .get({parent.kvar})
						add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{owncvar}.cache({ctor}))"

				@ref = "{tvar}"

				if true
					add "{bvar}||({tvar}[{gsym('##up')}]={fragment.tvar})"

				# dont add cvar always!
				# rule is just "do we need our own cache?"
				if @dynamics:length or (@consumed:length and nodes:length)
					ownCache = yes

			elif !isReactive
				add "({ctor})"

			elif canInline
				@ref = tvar
				@bvar = parent.bvar
				add "{parent.bvar} || ({ctor})"
			else
				let key = option(:key)
				let cref = @cref ||= "{cvar}[{osym}]"

				if markWhenBuilt
					@bvar = tagvar('B')

				if isDynamicType
					if key
						add "{owncvar}={dynamicContextFn}({key.osym},{key.c})"
					else
						add "{owncvar}={renderContextFn}({type.osym})"
					let gets = "{owncvar}.run({type.c},{hasDynamicTagName ? 1 : 0})"
					add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{owncvar}.cache({ctor}))"

				elif key
					add "{owncvar}={renderContextFn}({key.osym})"
					let gets = "{owncvar}.run({key.c})"
					add "({bvar}={dvar}=1,{tvar}={gets}) || ({bvar}={dvar}=0,{owncvar}.cache({ctor}))"
				else
					let ref = "{parentCache}[{osym}]"
					if markWhenBuilt
						add "({bvar}={dvar}=1,{tvar}={ref}) || ({bvar}={dvar}=0,{ref}={ctor})"
					else
						add "({tvar}={ref}) || ({ref}={ctor})"

				if isDetached
					add "{bvar}||({tvar}[{gsym('##up')}]={fragment.tvar})"

				@ref = tvar

				if dynamicKey
					ownCache = yes

				if parent isa TagSwitchFragment
					ownCache = yes

			if ownCache
				@cvar = tvar # tagvar(:c)

		if isDynamicType
			add {'if': "{tvar}[{gsym('#isRichElement')}]"}
			# ctx:condition =

		if @slots
			for own name, slot of @slots
				STACK.use("slots")
				let fn = isDynamicType ? gsym('#getFunctionalSlot') : gsym('#getSlot')
				add "{slot.tvar} = {OP('.',tvar,fn).c}('{name}',{cvar})"

		let flagsToConcat = []

		for item in @attributes
			if item.@chain and item.@chain:length and !(item isa TagHandler)
				let mods = item.modifiers
				let dyn = !mods.isStatic

				let specials = mods.extractDynamics()
				let modid = item.modsIdentifier
				let modpath = modid ? OP('.',tvar,modid).c : "{cvar}[{mods.osym}]"

				if dyn
					add "{vvar} = {modpath} || ({mods.c(o)})"
					for special in specials
						let k = special.option(:key)
						let i = special.option(:index)
						add "{OP('.',vvar,k).c}[{i}]={special.c(o)}"
					add "{bvar} || ({modpath}={vvar})"
				else
					add "{bvar} || ({modpath}={mods.c(o)})"

			if !isReactive
				# buggy
				add item.c(o) # "{tvar}.{item.c(o)}"
			elif item.isStatic
				add "{bvar} || ({item.c(o)})"
			else
				let iref = "{cvar}[{item.osym}]"

				if item isa TagFlag
					let cond = item.condition
					let val = item.name
					let cref
					let vref
					let batched = !isDynamicType

					if cond and !cond.isPrimitive
						cref = "{cvar}[{cond.osym}]"
						add "({vvar}=({cond.c(o)}||undefined),{vvar}==={cref}||({dvar}|={F.DIFF_FLAGS},{cref}={vvar}))"

					if val and !(val isa Token) and !val.isPrimitive and !(val isa MixinIdentifier) and !(val isa StyleRuleSet)
						vref = "{cvar}[{val.osym}]"
						add "({vvar}={val.c(o)},{vvar}==={vref}||({dvar}|={F.DIFF_FLAGS},{vref}={vvar}))"

					if batched or true
						if cref and vref
							flagsToConcat.push("({cref} ? ({vref}||'') : '')")
						elif cref
							flagsToConcat.push("({cref} ? {val.c(as:'string')} : '')")
						elif vref
							flagsToConcat.push("({vref}||'')")
						elif val isa MixinIdentifier
							flagsToConcat.push(val.c(as:'string'))
						else
							flagsToConcat.push("'{val.c(as:'substring')}'")
					else
						if cref
							add "{tvar}.flags.toggle({vref ? vref : val.c(as:'string')},{cref}))"
						else
							add "({bvar}||{tvar}.flags.add({vref ? vref : val.c(as:'string')})"

				elif item isa TagHandler
					let mods = item.modifiers
					let specials = mods.extractDynamics()
					let visit = no
					add "{hvar} = {iref} || ({iref}={mods.c(o)})"
					for special in specials
						let k = special.option(:key)
						let i = special.option(:index)
						let path = "{OP('.',hvar,k).c}[{i}]"
						if k == 'options'
							visit = yes
							add "({vvar}={special.c(o)},{vvar}==={path} || ({path}={vvar},{dvar}|={F.DIFF_MODIFIERS}|{F.DIFF_INLINE}))"
						else
							add "{path}={special.c(o)}"

					add "{bvar} || {ref}.on$({item.quoted},{hvar.c},{scope__.context.c})"
					if visit
						add "{dvar}&{F.DIFF_INLINE} && ({dvar}^={F.DIFF_INLINE},{hvar}[{gsym('#visit')}]?.())"

				elif item isa TagAttr and item.ns == 'bind'

					let rawVal = item.value
					let val = PATHIFY(rawVal)

					shouldEnd = yes
					if val isa Array
						let target = val[0]
						let key = val[1]
						let bval = "[]"

						let alit = target and target.isConstant #  isa Literal or target isa ScopeContext
						let blit = key and key.isConstant # isa Literal or key isa SymbolIdentifier

						if target isa Self and !root.isSelf
							alit = false

						if alit and blit
							bval = "[{target.c(o)},{key.c(o)}]"

						elif blit
							bval = "[null,{key.c(o)}]"

						add "{vvar}={iref} || ({iref}={ref}.bind$('{item.key}',{bval}))"

						if target and !alit
							add "{vvar}[0]={target.c(o)}"
						if key and !blit
							add "{vvar}[1]={key.c(o)}"

					elif val isa Variable
						let getter = "function()\{ return {val.c(o)} \}"
						let setter = "function(v$)\{ {val.c(o)} = v$ \}"
						let bval = "\{get:{getter},set:{setter}\}"
						add "{bvar} || {ref}.bind$('{item.key}',{bval})"
				else
					item.option(svg: true) if isSVG
					let val = item.value
					if item.valueIsStatic
						add "{bvar} || ({M(item.js(o),item)})"
					elif val isa Func
						add "({item.js(o)})"
					elif val.@variable
						let vc = val.c(o)
						item.value = LIT("{iref}={vc}")
						add "({vc}==={iref} || ({M item.js(o),item}))"
					else
						item.value = LIT("{iref}={vvar}")
						add "({vvar}={val.c(o)},{vvar}==={iref} || ({M item.js(o),item}))"

		if flagsToConcat:length or ((isSelf or isDynamicType) && @className)
			flagsToConcat.unshift(@className) if @className
			let cond = "{dvar}&{F.DIFF_FLAGS}"
			let meth = isSelf ? 'flagSelf$' : 'flag$'
			let extra = 'null'
			cond = "(!{bvar}||{cond})" if isSelf or isDynamicType

			if isDynamicType
				if @styleName
					extra = @styleName.c

				add "({cond} && {tvar}.flags.reconcile({osym},{flagsToConcat.join("+' '+")},{extra}))"
			else
				add "({cond} && {tvar}.{meth}({flagsToConcat.join("+' '+")},{extra}))"

		# When there is only one value and that value is a static string or num - include it in ctor
		# loop through attributes etc
		# add

		let count = nodes:length

		for item in nodes
			if item isa Str # static for sure
				# should this not go into a TagLike? Definitely
				if isReactive
					add "{bvar} || {tvar}{domCall('insert')}({item.c(o)})"
				else
					add "{tvar}{domCall('insert')}({item.c(o)})"
			elif item isa StyleRuleSet
				for ph in item.placeholders
					let item = ph.@setter
					# TODO - this logic should definitely move into TagAttr.c
					let iref = "{cvar}[{item.osym}]"
					let val = item.value
					if item.valueIsStatic
						add "{bvar} || ({M(item.js(o),item)})"
					elif val isa Func
						add "({item.js(o)})"
					elif val.@variable
						let vc = val.c(o)
						item.value = LIT("{iref}={vc}")
						add "({vc}==={iref} || ({M item.js(o),item}))"
					else
						item.value = LIT("{iref}={vvar}")
						add "({vvar}={val.c(o)},{vvar}==={iref} || ({M item.js(o),item}))"

			else
				add item.c(o)

		if shouldEnd
			if !parent and !isSelf
				foot.push("{bvar} || {parentCache}.sym || !{tvar}.setup || {tvar}.setup({dvar},{parentCache},{osym})")
				foot.push("{parentCache}.sym || {tvar}{domCall 'end'}({dvar})")
			elif isSelf
				foot.push("{tvar}{domCall 'close'}({dvar})")
			else
				foot.push("{bvar} || !{tvar}.setup || {tvar}.setup({dvar},{owncvar})")
				foot.push("{tvar}{domCall 'end'}({dvar})")

		# horrible hacks to work around the way we join the tag parts
		# to expressions and/or statements
		if isDynamicType
			foot.push(endif: true)

		if parent isa TagLoopFragment

			if parent.isKeyed
				# the last kvar argument here is not used right now
				foot.push "{parent.ref}.push({tvar},{parent.kvar}++,{kvar})"
			elif parent.isIndexed
				foot.push "{parent.kvar}++"

		elif isFragment and parent and !(parent isa TagSwitchFragment)
			yes

		elif parent and !(parent isa TagSwitchFragment) and (isComponent or dynamicKey or option(:reference))
			let pref = fragment.ref
			let cref = @cref
			
			if dynamicKey or isDynamicType or isDetached
				if fragment isa TagSlotProxy
					foot.push "({tvar}=={cref}) || (!{cref} && {pref}{domCall('appendChild')}({cref}={tvar})) || ({pref}{domCall('replaceChild')}({tvar},{cref}),{cref}={tvar})"
				else
					foot.push "({tvar}=={cref}) || (!{cref} && ({cref}={tvar}){domCall('insertInto')}({pref})) || {cref}{domCall('replaceWith')}({cref}={tvar},{pref})"
			elif !isDetached
				foot.push "{bvar} || {pref}{domCall('appendChild')}({tvar})"

		if option(:fragmented)

			add "{runtime:renderContext}.context=null"

		if !@consumedBy
			if option(:return) or option(:iife)
				foot.push "return {tvar}"
			elif !isReactive or o:inline
				foot.push "{tvar}"

		out = out.concat(foot)

		if o:inline
			o:inline = parentIsInlined

			let js = '('
			let last = out:length - 1

			for item,i in out
				if item:if
					js += "({item:if} && (\n" # + '{\n'
				else
					js += item:endif ? '))' : item
					js += ',\n' unless i == last or (out[i + 1]:endif)
			js += ')'

			# let js = '(' + out.join(',\n') + ')'
			if isSlot and hasChildren
				let post = ""
				unless parent isa TagSwitchFragment
					let key = "{cvar}[{osym}]"
					let key_ = "{cvar}[{osym '_'}]"
					let key__ = "{cvar}[{osym '__'}]"
					let post = "{tvar}==={key__} || ({key_} = {fragment.tvar}{domCall('insert')}({key__}={tvar},{@flags},{key_}))"
				js = "({tvar}={slotPath}),(!{tvar} || !{tvar}.hasChildNodes() && {js}),({post})"
			return js

		o:inline = parentIsInlined

		let js = ''
		for item in out
			if item:if
				js += "if({item:if})" + '{\n'
			elif item:endif
				js += '};\n'
			else
				js += item + ';\n'

		# let js = out.join(";\n")
		if isSlot and hasChildren
			let post = ""
			unless parent isa TagSwitchFragment
				let key = "{cvar}[{osym}]"
				let key_ = "{cvar}[{osym '_'}]"
				let key__ = "{cvar}[{osym '__'}]"
				post = "{tvar}==={key__} || ({key_} = {fragment.tvar}{domCall('insert')}({key__}={tvar},{@flags},{key_}))"

			js = "{tvar}={slotPath};\nif(!{tvar} || !{tvar}.hasChildNodes())\{\n{js}\n\}\n{post}"

		if option(:iife)
			js = "(()=>\{{js};\})()"
			js = "return {js}" if option(:return)
		elif hasBlockScopedVariables
			# only if we are not in expression?
			js = '{' + js + '}'
		return js

export class TagWrapper < ValueNode

	def visit
		if value isa Array
			value.map(|v| v.traverse)
		else
			value.traverse
		self

	def c
		"{scope__.imba.c}.getTagForDom({value.c(expression: yes)})"

# SELECTORS

export class Selector < ListNode

	def initialize list, options
		@nodes = list or []
		@options = options

	def add part, typ
		push(part)
		self

	def isExpressable
		yes

	def visit
		for item in @nodes
			item.traverse unless item isa Token

	def query
		var str = ""
		var ary = []

		for item in nodes
			var val = item.c
			if item isa Token
				ary.push("'" + val.replace(/\'/g,'"') + "'")
			else
				ary.push(val)

		return ary.join(' + ')

	def toString
		AST.cary(nodes).join('')

	def js o
		var typ = option(:type)
		var q = AST.c(query)
		var imba = scope__.imba.c

		if typ == '%'
			"{imba}.q$({q},{o.scope.context.c(explicit: yes)})" # explicit context
		elif typ == '%%'
			"{imba}.q$$({q},{o.scope.context.c(explicit: yes)})"
		else
			"{imba}.q{typ}({q})"

export class SelectorPart < ValueNode

# DEFER

export class Await < ValueNode

	prop func

	def js o
		return "await {value.c}" # if option(:native)
		# introduce a util here, no?
		CALL(OP('.',Util.Promisify.new([value]),'then'),[func]).c

	def visit o
		# things are now traversed in a somewhat chaotic order. Need to tighten
		# Create await function - push this value up to block, take the outer
		value.traverse

		var fnscope = o.up(Func) # do |item| item isa MethodDeclaration or item isa Fun

		if fnscope
			fnscope.set(async: yes)

		return self

		###
		Top-level await is supported from node 14.8.0 but only when
		using loading script as es module, which breaks require etc.
		Right now we use our old async func transformations for top-level awaits
		but that feels hacky.
		###
		warn "toplevel await not allowed"

		var block = o.up(Block) # or up to the closest FUNCTION?
		var outer = o.relative(block,1)
		var par = o.relative(self,-1)

		func = AsyncFunc.new([],[])
		# now we move this node up to the block
		func.body.nodes = block.defers(outer,self)
		func.scope.visit

		# if the outer is a var-assignment, we can simply set the params
		if par isa Assign
			par.left.traverse
			var lft = par.left.node
			# Can be a tuple as well, no?
			if lft isa VarReference
				# the param is already registered?
				# should not force the name already??
				# beware of bugs
				func.params.at(0,yes,lft.variable.name)
			else
				par.right = func.params.at(0,yes)
				func.body.unshift(par)
				func.scope.context

		# If it is an advance tuple or something, it should be possible to
		# feed in the paramlist, and let the tuple handle it as if it was any
		# other value

		# CASE If this is a tuple / multiset with more than one async value
		# we need to think differently.

		# now we need to visit the function as well
		func.traverse
		# pull the outer in
		self

export class AsyncFunc < Func

	def initialize params, body, name, target, options
		super(params,body,name,target,options)

	def scopetype do LambdaScope

# IMPORTS
export class ESMSpecifier < Node
	prop alias
	prop name

	def loc
		@alias ? @alias.loc : @name.loc

	def initialize name, alias
		@name = name
		@alias = alias

	def sourcePath
		@importer ? @importer.sourcePath : null

	def visit stack
		@declaration = stack.up(ESMDeclaration)
		if @declaration isa ImportDeclaration
			@importer = @declaration
		else
			@exporter = @declaration
		@cname = helpers.clearLocationMarkers(@name.c())
		@key = @alias ? helpers.clearLocationMarkers(@alias.c()) : @cname

		if @exporter
			# lookup variable
			unless @exporter.source
				@variable = scope__.root.lookup(@cname)
		else
			@variable = scope__.root.register(@key, self, type: 'imported')
		self

	def js
		let n = helpers.toValidIdentifier(@name.c)
		let a = @alias and helpers.toValidIdentifier(@alias.c)
		if a
			"{n} as {a}"
		else
			"{n}"

export class ImportSpecifier < ESMSpecifier

export class ImportNamespaceSpecifier < ESMSpecifier

export class ExportSpecifier < ESMSpecifier

export class ExportAllSpecifier < ESMSpecifier

export class ImportDefaultSpecifier < ESMSpecifier

export class ESMSpecifierList < ListNode

	def js
		'{' + super + '}'

export class ESMDeclaration < Statement
	prop variable
	prop source

	def initialize keyword, specifiers, source
		setup
		@keyword = keyword
		@specifiers = specifiers
		@source = source
		@defaults = (specifiers and specifiers.find(do $1 isa ImportDefaultSpecifier))

	def addEnv env
		@envs ||= []
		@envs.push( EnvFlag.new env)
		self

	def isExcluded
		if isTypeOnly and !STACK.tsc
			return yes

		if @envs
			if STACK.tsc
				return @envs.find(do $1.@key == 'JS')

			return !@envs.find(do $1.isTruthy)
		return no

	def isTypeOnly
		no

	def isExport
		String(keyword) == 'export'

	def js
		let kw = M(keyword.c,keyword)
		if @specifiers and @source
			"{kw} {AST.cary(@specifiers).join(',')} from {@source.c}"
		elif @specifiers
			"{kw} {AST.cary(@specifiers).join(',')}"
		elif @source
			"{kw} {@source.c}"

export class AssetReference < ValueNode

	def setup
		self

	def asset
		@value

	def c
		let out = ""
		let ref = value:ref.c
		let path = value:path
		if asset:kind and path.indexOf('?') == -1
			path += "?{asset:kind}"
		if STACK.tsc
			if value:pathToken
				# value:pathToken
				let pathjs = M("'{path.split('?')[0]}'",value:pathToken)
				out = "import {pathjs}; const {ref} = /** @type\{ImbaAsset\} */(null)"
			else
				# out = "const {ref} = /** @type\{ImbaAsset\} */(\{path:'{path}'\})"
				# out = "const {ref} = /** @type\{ImbaAsset\} */(\{path:'{path}'\})"
				out = "const {ref} = /** @type\{ImbaAsset\} */(\{path:'{path}'\})"
		else
			out = "import {ref} from {M("'{path}'",value:pathToken)}"
		return out

export class ImportDeclaration < ESMDeclaration

	def sourcePath
		@source and @source.c()

	def ownjs
		var src = @source and @source.c()

		if STACK.tsc
			# let raw = src.slice(1,-1)
			let [raw,q] = @source.raw.split('?')
			src = M("'{raw}'",@source)

			if raw.match(/\.(html|svg|png|jpe?g|gif)$/) or (q and q.match(/^\w/) and q != 'external')
				if @specifiers and @source
					let out = "{M(keyword.c,keyword)} {src};\nimport {AST.cary(@specifiers).join(',')} from 'data:text/asset;';"
					return out

		let type = isTypeOnly ? ' type ' : ' '
		if @specifiers and @source
			"{M(keyword.c,keyword)}{type}{AST.cary(@specifiers).join(',')} from {src}"
		else
			"{M(keyword.c,keyword)}{type}{src}"

	def js
		return "" if isExcluded

		let out = ownjs
		return out

	def push next
		let curr = (@next or self)
		@up.replace curr, [curr,BR,@next = next]

	def visit
		setEnds(@keyword,@source)
		return if isExcluded

		for item in @specifiers
			item?.traverse()

		scope__.@lastImport = self
		@up = up
		return

export class ImportTypeDeclaration < ImportDeclaration

	def isTypeOnly
		yes

	def js2
		return "" unless STACK.tsc

		let src = @source.c

		if @defaults
			let tpl = '/** @typedef \{import(SOURCE).default\} NAME */true'
			tpl = tpl.replace('SOURCE',src).replace('NAME',@defaults.c)
			return tpl # '/** @typedef \{import("PATH")\} NAME */'
		else
			let parts = []

			for item in @specifiers[0].nodes
				let name = item.@name.c
				let alias = item.@alias ? item.@alias.c : item.@name.c
				let part = "/** @typedef \{import({src}).{name}\} {alias} */true"
				parts.push(part)
				# let part = tpl.replace('SOURCE',src).replace('PATH',name).replace('PATH',name)
			return parts.join(';\n')

export class ExportDeclaration < ESMDeclaration

	def visit
		return if isExcluded
		for item in @specifiers
			item?.traverse()
		self

	def js
		return "" if isExcluded
		let kw = M(keyword.c,keyword)

		if @specifiers and @source
			"{kw} {AST.cary(@specifiers).join(',')} from {@source.c}"
		elif @specifiers
			"{kw} {AST.cary(@specifiers).join(',')}"
		elif @source
			"{kw} {@source.c}"

export class ExportAllDeclaration < ExportDeclaration
export class ExportNamedDeclaration < ExportDeclaration

export class MixinReference
	prop name
	prop scope

	prop options
	prop rule

	def initialize name, scope
		@name = name
		@scope = scope
		@options = {}

export class MixinExports < Node

	def add name, val
		@mixins ||= {}
		@mixins[name] = val
		self

	def c
		"export const mixins$ = {AST.compileRaw(@mixins or {})}"

export class Export < ValueNode

	def loc
		let kw = option(:keyword)
		kw and kw:region ? kw.region : super

	def consume node
		if node isa Return
			option('return',yes)
			return self
		super

	def visit
		value.set(
			export: (option(:keyword) or self),
			return: option(:return),
			default: option(:default)
		)

		super

	def js o
		# p "Export {value}"
		# value.set export: self, return: option(:return), default: option(:default)

		# if value isa VarOrAccess
		# 	return "exports.{value.c} = {value.c};"
		let isDefault = option(:default)

		if value isa ListNode
			value.map do |item| item.set export: self
		# else
		#	value.set export: self

		if value isa MethodDeclaration or value isa ClassDeclaration
			return value.c

		if value isa Assign and value.left isa VarReference
			let ek = M('export',option(:keyword))
			let dk = isDefault and M('default',option(:default))
			return isDefault ? "{ek} {dk} {value.c}" : "{ek} {value.c}"

		if isDefault
			let out = value.c
			return "export default {out}"
		return value.c

export class Require < ValueNode

	def js o
		var val = value isa Parens ? value.value : value
		var out = val.c()
		out == 'require' ? 'require' : "require({out})"

export class EnvFlag < ValueNode

	def initialize
		super
		@key = String(@value).slice(1,-1)		

	def raw
		@raw ?= STACK.env("" + @key)

	def isTruthy
		if @key == "JS"
			return !STACK.tsc

		if STACK.tsc
			# always truthy for tsc unless $js$
			return true

		var val = raw
		return !!val if val !== undefined and !(val isa Node)
		return undefined

	def loc
		[0,0]

	def c
		var val = raw
		var out = val
		if STACK.tsc
			out = LIT("{runtime:$env}({STR(@key).c})")
		elif @key == "JS"
			out = LIT('true')
		elif val !== undefined
			if val isa String
				if val.match(/^\d+(\.\d+)?$/)
					out = String(parseFloat(val))
				else
					out = "'{val}'"
			elif val isa Node
				out = out.c
			else
				out = "{val}"
		else
			out = "globalThis.IMBA_ENV_{@key}"

		return M(out,@value)

export class StyleNode < Node

export class StyleSelector < StyleNode

# all weird parts of a selector? Or do we just compile it?

export class StyleRuleSet < StyleNode

	# selector
	def initialize selectors, body
		@placeholders = []
		@selectors = selectors
		@body = body

	def isStatic
		true

	def isGlobal
		!!option(:global)

	def addPlaceholder item
		@placeholders.push(item)
		return self

	def placeholders
		@placeholders

	def cssid
		@cssid ||= "{STACK.root.sourceId}-{tid}"

	def visit stack, o
		let cmp = @tagDeclaration = stack.up(TagDeclaration)

		let tags = stack.parents(TagLike)

		if tags[0] and cmp and tags[0].isSelf and tags[1]
			tags[0] = cmp

		if tags:length == 0 and cmp
			tags = [cmp]

		@css = {}
		@flag = stack.up(TagFlag)
		@tag = @flag and @flag.@tag

		let keywordName = String(option(:name) || '')
		if keywordName[0] == '%'
			# need to be safely converted to a reference? Can do that later
			@mixin = scope__.mixin(keywordName.slice(1))
			@mixin.rule = self
			@mixin.options:id = cssid

		if option(:export)
			STACK.root.mixinExports.add(@mixin.name,@mixin.options)

		let sel = String(@selectors).trim

		if stack.parent isa ClassBody
			# Declaration in a tag declaration
			let owner = stack.up(2)
			if owner isa TagDeclaration
				@css:type = 'component'

				if !@variable
					@sel = sel or '&'
					@css:scope = cmp
			else
				throw "css not allowed in class declaration"

		elif stack.parent isa TagBody
			@tag = tags[tags:length - 1]
			@sel = sel or '&'
			@css:type = 'scoped'
			@css:scope = @tag

			# FIX the selector based on the tag
		elif option(:toplevel)
			let inbody = stack.up(TagBody)

			if inbody
				# Inside some logical nesting
				@tag = stack.up(TagLike)
				@sel = sel or '&'

				@css:scope = @tag
				@css:ns = cssid
				@css:id = cssid
				@css:type = 'scoped'
				@name = cssid
				set(inTagTree: yes)

			else
				@css:scope = isGlobal ? null : scope__.closure
				@sel ||= sel

		elif o:rule
			@sel ||= @selectors?.toString.trim
			# console.log "inside other rule? {@sel} | {o:rule.@sel} |"
			@sel = "& {@sel}" if @sel.indexOf('&') == -1

		elif !@name and @tag and @flag and !@flag.@condition
			@css:scope = @tag
			@name = @tag.cssid
			@sel = "&"

		elif !@name
			@name = cssid # (cmp ? (cmp.cssns + oid) : (sourceId + oid))
			@sel = ".{@name}"

		@selectors?.traverse

		@styles = {}

		@body?.traverse(rule: self, styles: @styles, rootRule: (o:rule or self))

		# add the placeholderes
		if @placeholders:length
			if option(:inTagTree)
				for ph in @placeholders
					let setter = TagStyleAttr.new(ph.name)
					setter.@tag = @tag
					setter.value = ph.runtimeValue
					setter.set(
						propname: ph.@propname
						unit: ph.option('unit')
						styleterm: ph
					)
					ph.@setter = setter
					setter.traverse
			elif !@flag
				for ph in @placeholders
					ph.warn "Only allowed inside tag tree"

		if o:rule and o:styles
			if o:styles[@sel]
				let base = o:styles[@sel]
				helpers.deepAssign(base,@styles)
			else
				o:styles[@sel] = @styles

		else
			let component = @tagDeclaration
			let opts = {
				selectors: []
				ns: @css:ns
				id: @css:id
				type: @css:type
				scope: @css:scope
				tags: tags
				component: cmp
				inline: !!@flag
				global: !!isGlobal
				mixins: {}
				apply: {}
				depth: @tag ? @tag.@level : 0
			}

			@css = StyleRule.new(null,@sel,@styles,opts).toString
			STACK.css.add @css, opts
		self

	def toRaw
		"{@name}"

	def c
		if option(:toplevel) and option(:export)
			# console.log "EXPORT??!",@identifier,@mixin,@name
			return ""

		if @tvar
			let out = ["{@tvar} = '{@name}'"]
			let add = do out.push($1)
			let cvar = @tag.cvar
			let bvar = @tag.bvar

			for ph in @placeholders

				let item = ph.@setter
				# TODO - this logic should definitely move into TagAttr.c
				let iref = "{cvar}[{item.osym}]"
				let val = item.value

				# TODO optimize the css variable setters
				if true
					add "{M(item.js(o),item)}"
				elif item.valueIsStatic
					add "{bvar} || ({M(item.js(o),item)})"
				elif val isa Func
					add "({item.js(o)})"
				elif val.@variable
					let vc = val.c(o)
					item.value = LIT("{iref}={vc}")
					add "({vc}==={iref} || ({M item.js(o),item}))"
				else
					item.value = LIT("{iref}={vvar}")
					add "({vvar}={val.c(o)},{vvar}==={iref} || ({M item.js(o),item}))"

			# console.log out.join('\n'),STACK.isExpression
			let expr = STACK.isExpression
			return expr ? "(" + out.join(',') + ")" : out.join(";\n")
			# return "{@tvar} = '{@flagIf}'"

		if STACK.tsc and @placeholders:length
			let out = []
			for ph in placeholders
				out.push ph.runtimeValue.c
			let expr = STACK.isExpression
			return expr ? "(" + out.join(',') + ")" : out.join(";\n")

		if option(:inClassBody) or option(:inTagTree) or option(:toplevel)
			return ''

		let out = "'{@name}'"
		return out

	# nodes # bunch of style properties and potentially nested rules

export class StyleBody < ListNode

	def visit
		let items = @nodes
		let i = 0

		let prevname
		for item in items when item isa StyleDeclaration
			if !item.@property.@name
				item.@property.name = prevname

			prevname = item.@property.@name

		while i < items:length
			let item = items[i]
			let res = item.traverse

			if res != item
				if res isa Array
					items.splice(i,1,*res)
					continue

			# has changed?
			if item == items[i]
				i++
		self

	def toJSON
		values

export class StyleDeclaration < StyleNode
	def initialize property, expr
		@property = property
		@expr = expr isa StyleExpressions ? expr : StyleExpressions.new(expr)
		self

	def clone name, params
		params = @expr.clone if params == null
		if typeof params == 'string' or typeof params == 'number'
			params = [params]
		if !(params isa Array) and (!(params isa ListNode) or params isa StyleOperation)
			params = [params]
		StyleDeclaration.new(@property.clone(name),params)

	def visit stack, o
		# see if property can be expanded
		let theme = stack.theme
		let list = stack.parent
		let name = String(@property.name)
		let alias = theme.expandProperty(name)
		if @expr
			@expr.traverse(
				rule: o:rule
				rootRule: o:rootRule,
				decl: self,
				property: @property
			)

		if alias isa Array
			list.replace(self,alias.map do clone($1))
			return
		elif alias and alias != name
			@property = @property.clone(alias)

		let method = String(alias or name).replace(/-/g,'_')

		
		@expr.traverse(decl: self, property: @property) if @expr


		let res
		let expanded = []
		if @property:isColor and @property.isColor
			res = theme.colormix(name.slice(1),@expr.toArray)
			# console.log "prop is color!!!",res

		elif theme[method] and !option(:plain)
			res = theme[method].apply(theme,@expr.toArray)

		if res isa Array
			@expr = StyleExpressions.new(res)
		elif res isa Object
			for own k,v of res
				if k.indexOf('&') >= 0
					let body = StyleBody.new([])
					let rule = StyleRuleSet.new(LIT(k),body)
					expanded.push(rule)
					for own k2,v2 of v
						# need recursive thing here
						body.add(clone(k2,v2))
				else
					expanded.push clone(k,v).set(plain: (k == name) or (k == alias))
			list.replace(self,expanded)
			return

		if @expr
			@expr.traverse(decl: self, property: @property)
			@expr.set(parens: no)

		if o:styles
			let key = @property.toKey
			
			let val = @expr
			if o:selector
				key = JSON.stringify([o:selector,key])

			if @property.isUnit
				if @property.number != 1
					val = LIT("calc({val.c} / {@property.number})")

			# if this key has already been set we need to delete it
			# because we rely on the key order of the object.
			# Should move over to using an array for this probably
			if o:styles[key]
				delete o:styles[key]

			o:styles[key] = val.c(property: @property)
		self

	def toCSS
		"{@property.c}: {AST.cary(@expr).join(' ')}"

	def toJSON
		toCSS

export class StyleProperty < StyleNode

	prop name
	prop number
	prop unit
	prop kind
	# modifiers
	# values
	def initialize token
		@token = token
		let raw = String(@token)

		# also split
		@parts = raw.replace(/(^|\b)\$/g,'--').split(/\b(?=[\^\.\@\!])/g) # .split(/[\.\@]/g)

		for part,i in @parts
			@parts[i] = part.replace(/^\.(?=[^\.])/,'@.')

		@name = String(@parts[0])

		if raw[0] == '#'
			@kind = 'color'
			if constants.HEX_REGEX.test(@name)
				error("Color name {@name} cannot be identical to valid hex color",loc: token[0] or token)


		if let m = @name.match(/^(\d+)([a-zA-Z]+)$/)
			@number = parseInt(m[1])
			@unit = m[2]

		if !@name.match(/^[\#\w\-]/)
			@parts.unshift(@name = null)

		self

	def name= value
		if let m = value.match(/^(\d+)([a-zA-Z]+)$/)
			@number = parseInt(m[1])
			@unit = m[2]
		else
			@number = @unit = null
		@name = value

	def name
		@name ||= String(@parts[0])

	def clone newname
		StyleProperty.new([newname or name].concat(modifiers).join(""))

	def addModifier modifier
		@parts.push(modifier)
		self

	def isUnit
		@unit

	def isColor
		@kind == 'color' or @name[0] == '#'

	def modifiers
		@parts.slice(1)

	def toJSON
		name + modifiers.join("§")

	def toString
		name + modifiers.join("§")

	def toKey
		let name = isUnit ? "--u_{@unit}" : (isColor ? "--c_{@name.slice(1)}" : self.name)
		return [name].concat(modifiers).join('§')

	def c
		toString

	# lookup shorthand. If shorthand represents multiple
	# props then we compile it to multiple props

export class StylePropertyIdentifier < StyleNode
	def initialize name
		@name = name
		if String(name)[0] == '$'
			@name = "--{String(name).slice(1)}"
		# val[0] == '$' ? "var(--{val.slice(1)})" : val

	def toJSON
		String(@name)

	def toString
		String(@name)

export class StylePropertyModifier < StyleNode
	def initialize name
		@name = name

	def toJSON
		String(@name)

	def toString
		String(@name)

export class StyleExpressions < ListNode

	def load list
		if list isa Array
			list = list.map do $1 isa StyleExpression ? $1 : StyleExpression.new($1)
		[].concat(list)

	def c o
		let out = AST.cary(@nodes,o).join(', ')
		if option(:parens)
			out = "( {out} )"
		return out

	def clone
		StyleExpressions.new(@nodes.slice(0))

	def toArray
		@nodes.filter(do $1 isa StyleExpression).map(do $1.toArray )

export class StyleExpression < ListNode

	def load list
		[].concat(list)

	def toString
		AST.cary(@nodes).join(' ')

	def toArray
		@nodes.slice(0)

	def clone
		StyleExpression.new(@nodes.slice(0))

	def c o
		if o and o:as == 'js'
			return AST.cary(@nodes,o).join(' ')
		toString

	def toJSON
		toString

	def toArray
		@nodes

	def toIterable
		@nodes

	def addParam param,op
		param.@op = op
		last.addParam(param)
		return self

	def reclaimParams
		let items = filter do $1:param
		for item in items
			let param = item:param
			let op = param.@op
			add([op,param],after: item)
			item.@params = []

		return

	def visit stack, o
		if o and o:property
			let name = o:property.@name
			if name == 'gt' or name == 'grid-template'
				reclaimParams
		super

export class StyleParens < ValueNode

	def visit stack, o
		super
		set(calc: !stack.up(StyleParens) and !stack.up(StyleFunction))

	def c o
		let plain = @value.c

		# TODO warn when option(:unit) is set

		if o and o:as == 'js'
			return plain
		elif option(:calc)
			let unit = @options and String(@options:unit or '')
			if unit

				return "calc(calc({plain}) * 1{unit})"
			else
				return "calc({plain})"

		else
			return "({plain})"

export class StyleOperation < ListNode
	def c o
		return AST.cary(@nodes,o).join(' ')

export class StyleTerm < ValueNode

	def valueOf
		String(@value)

	def toString
		String(@value)

	def toRaw
		valueOf

	def toAlpha
		valueOf

	def visit stack, o
		@token = @value
		@property = o:property
		@propname = o:property and o:property.@name
		self:alone = (stack.up isa StyleExpression) and stack.up.values:length == 1
		let resolved = stack.theme.$value(self,0,@propname)
		@resolvedValue = resolved unless (stack.up(StyleParens) or stack.up(StyleFunction))
		self

	get param
		@params and @params[0]

	def kind
		@kind

	def runtimeValue
		value

	def addParam param
		@params ||= []
		@params.push(param)
		return self

	def c o
		let out = (@resolvedValue and !(@resolvedValue isa Node)) ? C(@resolvedValue) : valueOf
		out

export class StyleInterpolationExpression < StyleTerm

	prop name

	def loc
		[@startLoc,@endLoc]

	def visit stack,o
		super
		if o:rootRule
			o:rootRule.addPlaceholder(self)
		@id = "{sourceId}_{tid}" # could use a different counter?
		@name = "--{@id}"
		@runtimeValue = value
		# @propname = stack.theme.expandProperty

	def runtimeValue
		@runtimeValue

	get unit
		@options and String(@options:unit) or ''

	def c
		"var(--{@id})"

export class StyleFunction < Node

	def kind
		'function'

	def initialize value, params
		@name = value
		@params = params

	def visit stack, o
		@property = o:property
		@propname = o:property and o:property.@name
		@params.traverse if @params and @params:traverse

		let name = String(@name)
		let parts = @params.toArray.flat

		if @property.isColor and name.match(/^(lch|rgba?|hsla?)$/)

			@lcha = []	
			for part,i in parts
				if part.@value == '/'
					continue
				@lcha.push(part)

			unless name == 'lch'
				let alpha = @lcha[3]
				let kind = name.slice(0,3)

				for part,i in @lcha
					if !(part isa StyleDimension) and i < 3
						return error("Dynamic part not allowed in non-lch #color definitions", loc: part)

				try
					let inside = @params.c
					if alpha and !(alpha isa StyleDimension)
						inside = inside.replace(alpha.c,'1')
					let full = "{name}({inside})"

					let col = colord(full).toLch()
					@lcha = [col:l,col:c,col:h,col:a]
					@lcha[3] = alpha if alpha
				catch e
					error("Failed to parse color", loc: self)
		self

	def lcha
		@lcha or [0,0,0,1]

	def toString
		c

	def c o

		let name = String(@name)
		let pars = @params.c
		let out = "{name}({pars})"

		if (@property and @property.isColor)
			
			if name == 'hsl'
				let parts = @params.toArray.flat
				if parts:length == 3
					return AST.cary(parts).join(',')

			if let res = Color.from(out)
				return res.toVar

		out = helpers.singlequote(out) if o and o:as == 'js'
		return out

export class StyleURL < ValueNode

	def c
		let out = String(@value)
		return SourceMapper.strip(out)

export class StyleIdentifier < StyleTerm
	prop color

	def visit stack
		let raw = toString
		if raw.match(/^[lcha]$/)
			super
			let mix = stack.up(StyleColorMix)
			@colormix = mix
			@resolvedValue = "var(--u_{@colormix.@name}{raw.toUpperCase()})"
		else
			if raw.match(/^([a-zA-Z]+\d+|black|white)$/)
				color = "{raw}"
				if self:param
					color += "/" + self:param.toAlpha
			super

	def c o
		if @colormix
			return "var(--u_{@colormix.@name}{toString.toUpperCase()})"

		if color
			let val = color.toString
			let asvar = option(:parameterize) or (@property and @property.isColor)
			let pre = asvar ? '/*##*/' : '/*#*/'
			return pre + val

		let val = toString
		if val[0] == '$'
			val = "var(--{val.slice(1)})"
			val = helpers.singlequote(val) if o and o:as == 'js'
			return val
		else
			super

export class StyleString < StyleTerm

export class StyleColor < StyleTerm

	def visit
		super
		let raw = toRaw
		let name = @name = raw.slice(1)

		# only needed for the property color?
		if let m = raw.match(constants.HEX_REGEX)
			@hex = yes
			let col = colord(raw).toLch()
			@lcha = [col:l,col:c,col:h,col:a]
		else
			@lcha = [
				"var(--u_{name}L)"
				"var(--u_{name}C)"
				"var(--u_{name}H)"
				"var(--u_{name}A,1)"
			]
		
		let a = self:param and self:param.toAlpha
		if a != null
			a = "var(--{a.slice(1)},100%)" if a[0] == '$'
			@lcha[3] = a

	def lcha
		@lcha

	def c o
		let raw = toRaw
		let name = raw.slice(1)
		let rich = Color.from(raw)

		if @property and @property.isColor
			console.log 'deprecated'
			return rich.toVar

		let [l,c,h,a] = @lcha
		if @hex and a == 1
			return raw

		return "lch({l} {c} {h} / {a})"

export class StyleColorMix < StyleTerm

	def params
		option('params')

	def lcha
		return @lcha

	def visit
		let pars = params.toArray.flat
		let name = @name = toRaw.slice(1)
		params.traverse
		super

		@lcha = []

		for part,i in pars
			if part.@value == '/'
				continue
			@lcha.push(part)

		if @lcha:length == 3
			@lcha.push LIT("var(--u_{name}A,1)")
		self

	def c o
		let raw = toRaw
		let name = raw.slice(1)
		let [l,c,h,a] = AST.cary(self.lcha())
		return "lch({l} {c} {h} / {a})"

export class StyleVar < StyleTerm

	def c o
		toString

var VALID_CSS_UNITS = 'cm mm Q in pc pt px em ex ch rem vw vh vmin vmax % s ms fr deg rad grad turn Hz kHz cqw cqh cqi cqb cqmin cqmax'.split(' ')

export class StyleDimension < StyleTerm

	prop unit
	prop number

	def initialize value
		@value = value
		let m = String(value).match(/^([\-\+]?[\d\.]*)([a-zA-Z]+|%)?$/)
		@number = parseFloat(m[1])
		@unit = m[2] or null

	def clone num = @number, unit = @unit
		let cloned = StyleDimension.new(value)
		cloned.@unit =  unit
		cloned.@number = num
		return cloned

	def visit stack
		if @unit and @unit.match(/^[lcha]$/)
			if @colormix = stack.up(StyleColorMix)
				@unit = "{@colormix.@name}{@unit.toUpperCase()}"
			# if par isa StyleDimension
			# 		let u = par:_unit
			# 		if u and regex.test(u)
			# 			par:_unit = "{@name}{u.toUpperCase()}"
		super

	def toString
		"{@number}{@unit or ''}"

	def toFloat pct = 0.01
		let num = @number
		if @unit == '%'
			num = num * pct
		return num

	def toRaw
		@unit ? toString : @number

	def c o
		let out = (@resolvedValue and !(@resolvedValue isa Node)) ? C(@resolvedValue) : valueOf
		out = helpers.singlequote(out) if o and o:as == 'js' and @unit
		out

	def valueOf
		
		if unit == 'u'
			return number * 4 + 'px'
		elif unit == null
			return number
		elif unit in VALID_CSS_UNITS
			return String(@value)
		else
			let fallback = @colormix ? '' : ",1{unit}"
			if number == 1
				return "var(--u_{unit}{fallback})"
			else
				return "calc(var(--u_{unit}{fallback}) * {@number})"
				# return String(@value)

	def toAlpha
		unless unit
			return number + '%'
		else
			return valueOf

export class StyleNumber < StyleDimension
	# prop unit
	# prop number
	# def initialize value,unit
	# 	super
	# 	@number = parseFloat(String(value))
	# 	@unit = null

	# def valueOf
	# 	if unit == 'u'
	# 		return number * 4 + 'px'
	# 	return String(@value)

	# def valueOf
	# 	number

# UTILS

export class Util < Node

	prop args

	def initialize args
		@args = args

	# this is how we deal with it now
	def self.extend a,b
		Util.Extend.new([a,b])

	def self.callImba scope, meth, args
		CALL(OP('.',scope.imba,Identifier.new(meth)),args)

	def self.repeat str, times
		var res = ''
		while times > 0
			if times % 2 == 1
				res += str
			str += str
			times >>= 1
		return res

	def self.keys obj
		var l = Const.new("Object")
		var r = Identifier.new("keys")
		CALL(OP('.',l,r),[obj])

	def self.len obj, cache
		var r = Identifier.new("length")
		var node = OP('.', obj, r)
		node.cache(force: yes, pool: 'len') if cache
		return node

	def self.indexOf lft, rgt
		var node = Util.IndexOf.new([lft,rgt])
		# node.cache(force: yes, type: 'iter') if cache
		return node

	def self.slice obj, a, b
		var slice = Identifier.new("slice")
		return CALL(OP('.',obj,slice),AST.compact([a,b]))

	def self.iterable obj, cache
		# return obj if STACK.tsc
		var node = Util.Iterable.new([obj])
		node.cache(force: yes, pool: 'iter') if cache and !STACK.tsc
		return node

	def self.counter start, cache
		# should it not rather be a variable?!?
		var node = Num.new(start) # make sure it really is a number
		node.cache(force: yes, pool: 'counter') if cache
		return node

	def self.array size, cache
		var node = Util.Array.new([size])
		node.cache(force: yes, pool: 'list') if cache
		return node

	def name
		'requireDefault$'

	def js
		called(@args,option(:stdlib))

	def called pars,std,name
		if std and !STACK.isStdLib
			# in typescript?
			"{STACK.corelib[std].c}({AST.cary(pars).join(',')})"
		else
			scope__.root.helper(self,helper)
			"{name or self.name}({AST.cary(pars).join(',')})"

var HELPERS = {
	setField: '''(target,key,value,o){
		Object.defineProperty(target,key,{value:value});
	};'''

	unit: '''(value,unit){
		return value + unit;
	};'''

	memo: '''(hash,slf,cb,scope = globalThis){
		let sym = Symbol.for(hash)
		let fn = scope[sym] ||= (cb.memoized=sym,cb)
		return slf == null ? fn : fn.bind(slf)
	};'''

	optNegIndex: '''(value,index){ return value ? value[value.length + index] : null };'''
	negIndex: '''(value,index){ return value[value.length + index] };'''

	extendTag: '''(el,cls){
		Object.defineProperties(el,Object.getOwnPropertyDescriptors(cls.prototype));
		return el;
	};'''

	initField: '''(target,key,o){
		Object.defineProperty(target,key,o);
	};'''

	watcher: '''(k,w){
		return { enumerable:true,
			set(v){var o=this[k]; (v===o)||(this[k]=v,this[w]({value:v,oldValue:o}));},
			get(){ return this[k] }
		};
	};'''

	decorate: {
		inline: '''(decorators,target,key,desc){
			var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
			else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
			return c > 3 && r && Object.defineProperty(target, key, r), r;
		};'''
		std: 'decorate$'
	}

	contains:
		inline: '''(a,b){
			const sym = Symbol.for("#has");
			return b && ( b[sym]?.(a) ?? b.includes?.(a) ?? b.has?.(a) ?? false);
		};'''
		std: 'has$'

	requireDefault: '''(obj){
		return obj && obj.__esModule ? obj : { default: obj };
	};'''

	virtualSuper: '''(target){
		var up = Object.getPrototypeOf(target);
		var supers = Object.getOwnPropertyDescriptors(target);

		const map = new WeakMap();
		const obj = Object.defineProperties(Object.create(up), supers);

		const proxy = {
			apply: (self, key, ...params) => { return obj[key].apply(self, params) },
			get: (self, key) => { return Reflect.get(obj, key, self); },
			set: (self, key, value, receiver) => { return Reflect.set(obj, key, value, self);}
		}

		return function (s) {
			return map.get(s) || map.set(s, new Proxy(s, proxy)) && map.get(s);
		}
	};'''
}

export class Util.Helper < Util
	def name
		option(:name)

	def helper
		option(:helper)

for own k,v of HELPERS
	Util[k] = do |*args|
		let helper = 'function ' + k + '$__' + (v:inline or v)
		# different helper when they are bundled?
		Util.Helper.new(args).set(name: k + '$__', helper: helper, stdlib: v:std or null)

export class Util.Extend < Util
	def helper
		'''
		function extend$__(target,ext){
			// @ts-ignore
			const descriptors = Object.getOwnPropertyDescriptors(ext);
			delete descriptors.constructor;
			if(target.extend__ instanceof Function){
				target.extend__(descriptors,ext);
			} else {
				// @ts-ignore
				Object.defineProperties(target,descriptors);
			}
			return target;
		};
		'''

	def js o
		called(AST.compact(args),'extend$','extend$__')

export class Util.IndexOf < Util

	def helper
		'''
		function idx$__(a,b){
			return (b && b.indexOf) ? b.indexOf(a) : [].indexOf.call(a,b);
		};
		'''

	def js o
		called(args,'idx$','idx$__')


export class Util.Is < Util

	def left
		args[0]

	def right
		args[1]

	def op
		args[2]

	def stdfn
		'is$'
		
	def helper
		'// @ts-ignore\nfunction is$(a,b){ return a === b || ' + "b?.[{symbolRef('#matcher')}]?.(a) || false" + '}'

	def clone b
		constructor.new(args[2] ? [args[0],b,args[2]] : [args[0],b])

	def js o
		return called(args,stdfn,stdfn)

export class Util.In < Util.Is

	def stdfn
		'has$'
		
	def helper
		'// @ts-ignore\nfunction has$(a,b){ const sym = Symbol.for("#has"); return b && ( b[sym]?.(a) ?? b.includes?.(a) ?? b.has?.(a) ?? false); }'

export class Util.Isa < Util.Is

	def helper
		'// @ts-ignore\nfunction isa$(a,b){ return typeof b === "string" ? (typeof a === b) : b[Symbol.hasInstance]?.(a) }'

	def js
		if right instanceof Str
			# only need parens if left is caching...
			return "typeof ({left.c})==={right.c}"
		
		if String(op) == 'instanceof' or STACK.tsc
			return "({left.c}) instanceof {right.c}"

		return called([left,right],'isa$','isa$')

export class Util.Promisify < Util

	def helper
		# should also check if it is a real promise
		'''
		function promise$__(a){
			if(a instanceof Array){
				console.warn("await (Array) is deprecated - use await Promise.all(Array)");
				return Promise.all(a);
			} else {
				return (a && a.then ? a : Promise.resolve(a));
			}
		}
		'''

	def js o
		scope__.root.helper(self,helper)
		"promise$__({args.map(|v| v.c).join(',')})"

export class Util.Iterable < Util

	def helper
		# now we want to allow null values as well - just return as empty collection
		# should be the same for for own of I guess
		"function iter$__(a)\{ let v; return a ? ((v=a.toIterable) ? v.call(a) : a) : a; \};"

	def js o
		return args[0].c if args[0] isa Arr # or if we know for sure that it is an array
		return called(args,'iterable$','iter$__')

export class Util.Array < Util

	def js o
		# When this is triggered, we need to add it to the top of file?
		"new Array({args.map(|v| v.c)})"

class Entities

	def initialize root
		@root = root
		@map = []
		return self

	def add path, object
		@map[path] = object
		unless @map.indexOf(object) >= 0
			@map.push(object)
		self

	def lookup path
		@map[path]

	def plain
		JSON.parse(JSON.stringify(@map))

	def toJSON
		@map

class RootEntities

	def initialize root
		@root = root
		@map = {}
		return self

	def add path, object
		@map[path] = object
		self

	def register entity
		var path = entity.namepath
		@map[path] ||= entity
		self

	def plain
		JSON.parse(JSON.stringify(@map))

	def toJSON
		@map

# SCOPES

# handles local variables, self etc. Should create references to outer scopes
# when needed etc.

# add class for annotations / registering methods, etc?
# class Interface

# should move the whole context-thingie right into scope
export class Scope

	prop level
	prop context
	prop node
	prop parent
	prop varmap
	prop varpool
	prop params
	prop head
	prop vars
	prop counter
	prop entities

	def p
		if STACK.loglevel > 0
			console.log(*arguments)
		self

	def oid
		@oid ||= STACK.generateId('')

	def tid
		@tid ||= STACK.generateId('tag')

	def stack
		STACK

	def kind
		@kind ||= self:constructor:name.replace('Scope','').toLowerCase()

	def initialize node, parent
		@nr = STACK.incr('scopes')
		@node = node
		@parent = parent
		@vars = ScopeVariables.new([])
		@entities = Entities.new(self)
		@head = [@vars]
		@meta = {}
		@annotations = []
		@closure = self
		@virtual = no
		@counter = 0
		@varmap  = {}
		@ampermap = {}
		@counters = {}
		@varpool = []
		@mixins = {}
		@refcounter = 0
		@declListeners = []
		@level = (parent ? parent.@level : -1) + 1
		setup

	def params= val
		@params = val
		@head.push(val) if @head.indexOf(val) == -1
		return val

	def runtime
		root.runtime

	def setup
		@selfless = yes

	def incr name = 'i'
		var val = @counters[name] ||= 0
		@counters[name]++
		return val

	def nextShortRef
		AST.counterToShortRef(@refcounter++)

	def staticsRef
		@staticsRef ||= declare('$statics$',CALL(STACK.corelib['statics$'],[context]))

	def memovar name, init
		@memovars ||= {}
		let item = @memovars[name]
		unless item
			item = @memovars[name] = declare(item,init)
			# temporary(null,{reuse: yes},"{name}")

		return item

	def mixin name
		@mixins[name] ||= MixinReference.new(name,self)

	# def cssMixinFlag name
	def captureVariableDeclarations blk
		let items = []
		@declListeners.push(items)
		blk()
		@declListeners.pop()
		return items		

	def meta key, value
		if value != undefined
			@meta[key] = value
			return self
		@meta[key]

	def namepath
		'?'

	def cssid
		@cssid ||= "{root.sourceId}-{tid}"

	def cssns
		@cssns ||= "{root.sourceId}_{tid}"

	def tagCache
		unless @tagCache
			var cache = LIT("{runtime:getRenderContext}()")
			if STACK.closure isa IsolatedFunctionScope
				cache = LIT('{}')

			@tagCache = declare('ϲτ',
				cache,
				system: yes
				temporary: yes
				alias: 'ϲτ'
			)
		return @tagCache

	def tagTempCache
		@tagTempCache ||= declare('ϲττ',
			LIT('{}'),
			system: yes
			temporary: yes
			alias: 'ϲττ'
		)
	# def context
	# 	@context ||= ScopeContext.new(self)

	def context
		# why do we need to make sure it is referenced?
		unless @context
			if selfless
				@context = parent.context.fromScope(self)
				# @context.reference(self)
			else
				@context = ScopeContext.new(self)
		return @context

	def isInExtend
		closure.node.option('extension')

	def traverse
		self

	def visit
		return self if @parent
		@parent = STACK.scope(1) # the parent scope
		@level = STACK.scopes:length - 1

		STACK.addScope(self)
		root.scopes.push(self)
		self

	def wrap scope
		@parent = scope.@parent
		scope.@parent = self
		self

	# called for scopes that are not real scopes in js
	# must ensure that the local variables inside of the scopes do not
	# collide with variables in outer scopes -- rename if needed
	def virtualize
		self

	def root
		return STACK.ROOT

		var scope = self
		while scope
			return scope if scope isa RootScope
			scope = scope.parent
		return null

	def register name, decl = null, o = {}
		# FIXME re-registering a variable should really return the existing one
		# Again, here we should not really have to deal with system-generated vars
		# But again, it is important

		if !name
			o:system = yes

		if o:system
			return (o:varclass or SystemVariable).new(self,name,decl,o)

		name = AST.sym(name)

		# also look at outer scopes if this is not closed?
		var existing = @varmap.hasOwnProperty(name) && @varmap[name]

		if existing
			if decl and existing.type != 'global'
				decl.error('Cannot redeclare variable')
			# console.log 'redeclaring variable',"{existing} {decl}",existing.type

		# FIXME check if existing is required to be unique as well?
		if existing and !o:unique and existing.type != 'global'
			return existing

		let par = o:lookup && parent && parent.lookup(name)

		# var type = o:system ? SystemVariable : Variable
		var item = (o:varclass or Variable).new(self,name,decl,o)

		if par
			item.@parent = par

		if !o:system and (!existing or existing.type == 'global')
			@varmap[name] = item

		if STACK.state and STACK.state:variables isa Array
			STACK.state:variables.push(item)

		if @declListeners:length
			for l in @declListeners
				l.push(item)
		return item

	def annotate obj
		@annotations.push(obj)
		self

	# just like register, but we automatically
	def declare name, init = null, o = {}
		var variable = name isa Variable ? name : register(name,null,o)
		# TODO create the variabledeclaration here instead?
		# if this is a sysvar we need it to be renameable
		var dec = @vars.add(variable,init)
		variable.declarator ||= dec
		return variable

	def reusevar name
		temporary(null,{reuse: yes},name)

	# what are the differences here? omj
	# we only need a temporary thing with defaults -- that is all
	# change these values, no?
	def temporary decl, o = {}, name = null
		if @systemscope and @systemscope != self
			return @systemscope.temporary(decl,o,name)

		name ||= o:name
		o:temporary = yes
		if name and o:reuse and @vars["_temp_{name}"]
			return @vars["_temp_{name}"]

		if o:pool
			for v in @varpool
				if v.pool == o:pool && v.declarator == null
					return v.reuse(decl)

		var item = SystemVariable.new(self,name,decl,o)
		@varpool.push(item) # It should not be in the pool unless explicitly put there?
		@vars.push(item) unless o:nodecl # WARN variables should not go directly into a declaration-list
		if name and o:reuse
			@vars["_temp_{name}"] = item
		return item

	def lookup name
		@lookups ||= {}
		var ret = null
		name = AST.sym(name)
		if @varmap.hasOwnProperty(name)
			ret = @varmap[name]
		else
			ret = parent && parent.lookup(name)

			if ret
				@nonlocals ||= {}
				@nonlocals[name] = ret
		ret

	def requires path, name = ''
		root.requires(path,name)

	def imba
		# should be the same for node and js
		STACK.meta:universal = no
		@imba ||= (STACK.isNode ? LIT("(this && this[{root.symbolRef('#imba').c}] || globalThis[{root.symbolRef('#imba').c}])") : LIT('imba'))

	def autodeclare variable
		vars.add(variable) # only if it does not exist here!!!

	def free variable
		variable.free # :owner = null
		# @varpool.push(variable)
		self

	def selfless
		!!@selfless

	def closure
		@closure

	def finalize
		self

	def klass
		var scope = self
		while scope
			scope = scope.parent
			return scope if scope isa ClassScope
		return null

	def c o = {}
		o:expression = no
		node.body.head = head
		var body = node.body.c(o)

	def region
		node.body.region

	def loc
		node.loc

	def dump
		var vars = Object.keys(@varmap).map do |k|
			var v = @varmap[k]
			# unless v.@declarator isa Scope
			# 	console.log v.name, v.@declarator:constructor:name
			# AST.dump(v)
			v.references:length ? AST.dump(v) : null

		var desc =
			nr: @nr
			type: self:constructor:name
			level: (level or 0)
			vars: AST.compact(vars)
			loc: loc

		return desc

	def toJSON
		dump

	def toString
		"{self:constructor:name}"

	def closeScope
		self

# RootScope is wrong? Rather TopScope or ProgramScope
export class RootScope < Scope

	prop warnings
	prop scopes
	prop entities
	prop object
	prop options
	prop assets
	prop document

	def initialize
		super

		self.GLOBAL = register('global', self, type: 'global')
		self.GLOBAL.@c = 'globalThis'

		self.REQUIRE = register('require', self, type: 'global') # .@c = 'globalThis'
		self.IMPORT = register('import', self, type: 'global')
		self.MODULE = register('module', self, type: 'global')
		self.ASSERT = register('assert', self, type: 'global')
		self.EQ = register('eq', self, type: 'global')

		self.L = register('L', self, type: 'global')

		# register 'imba', self, type: 'global', varclass: GlobalReference
		register 'window', self, type: 'global', varclass: WindowReference
		self.document = register 'document', self, type: 'global', varclass: DocumentReference
		register 'exports', self, type: 'global'
		register 'console', self, type: 'global'
		register 'process', self, type: 'global'
		register 'parseInt', self, type: 'global'
		register 'parseFloat', self, type: 'global'
		register 'setTimeout', self, type: 'global'
		register 'setInterval', self, type: 'global'
		register 'setImmediate', self, type: 'global'
		register 'clearTimeout', self, type: 'global'
		register 'clearInterval', self, type: 'global'
		register 'clearImmediate', self, type: 'global'
		register 'globalThis', self, type: 'global'
		register 'isNaN', self, type: 'global'
		register 'isFinite', self, type: 'global'
		register '__dirname', self, type: 'global'
		register '__filename', self, type: 'global'
		register('__realname', self, type: 'global').@c = "__filename"
		register('__pure__', self, type: 'global', varclass: PureReference).@c = "/* @__PURE__ */"

		register '_', self, type: 'global'

		# preregister global special variables here
		@requires = {}
		@warnings = []
		@scopes   = []
		@helpers  = []

		@assets = {}
		@selfless = yes
		@implicitAccessors = []
		@entities = RootEntities.new(self)
		@object = Obj.wrap({})
		@head = [@vars]
		@symbolRefs = {}
		@importProxies = {}
		@vars.split = yes

		@imba = register('imba', self, type: 'global', varclass: ImbaRuntime, path: 'imba')
		@runtime = @imba.proxy
		self

	def importProxy name, path, pre = "${name}$"
		@importProxies[name] ||= register(pre, self, type: 'global', varclass: ImportProxy, path: path or name)

	def runtime
		@runtime

	def use item
		unless STACK.tsc
			@imba.touch("use_{item}")

	def sourceId
		@sourceId ||= STACK.sourceId # @options:sourcePath and helpers.identifierForPath(@options:sourcePath)

	def cssns
		@cssns ||= "{sourceId}_"

	# single-file-component options
	def sfco
		@sfco ||= declare('sfc$',LIT('{/*$sfc$*/}'))
	
	def l
		@l ||= declare('l$',LIT('null'))

	def context
		@context ||= RootScopeContext.new(self)

	def globalRef
		@globalRef ||= LIT('globalThis')

	def mixinExports
		unless @mixinExports
			@head.push(@mixinExports = MixinExports.new())
		return @mixinExports

	def registerAsset path, kind, context, pathToken
		let key = path + kind

		if @assets[key]
			return @assets[key]

		# console.log 'registering asset',!!STACK.lastImport
		let insertAt = STACK.lastImport or head

		let asset = @assets[key] = {
			path: path,
			kind: kind,
			external: yes,
			context: context
			pathToken: pathToken,
			ref: register('asset',null,system: yes)
		}

		insertAt.push(AssetReference.new(asset))
		return asset

	def lookup name
		name = AST.sym(name)
		@varmap[name] if @varmap.hasOwnProperty(name)

	def visit
		STACK.addScope(self)
		self

	def helper typ, value
		# log "add helper",typ,value
		if @helpers.indexOf(value) == -1
			@helpers.push(value)
			# console.log 'adding helper',value
			# @head.unshift(value)

		return self

	def head
		@head

	def dump
		var obj = {
			autoself: @implicitAccessors.map(|s| s.dump)
		}

		if OPTS:analysis:scopes
			var scopes = @scopes.map(|s| s.dump)
			scopes.unshift(super.dump)
			obj:scopes = scopes

		if OPTS:analysis:entities
			obj:entities = @entities

		return obj

	# not yet used
	def requires path, name
		if var variable = lookup(name)
			return variable

		if var variable = @requires[name]
			if variable.@requirePath != path
				throw Error.new("{name} is already defined as require('{variable.@requirePath}')")
			return variable

		var req = Require.new(Str.new("'" + path + "'"))
		var variable = Variable.new(self,name,null,system: yes)
		var dec = @vars.add(variable, req)
		variable.declarator ||= dec
		variable.@requirePath = path
		@requires[name] = variable
		return variable

	def imba
		@imba

	def symbolRef name
		name = SourceMapper.strip(name)

		if STACK.tsc
			return @symbolRefs[name] ||= Identifier.new(name.slice(1) + "_$INTERNAL$_")

		let map = @symbolRefs
		let alias = toJSIdentifier(name)
		map[name] ||= declare(null,LIT("Symbol.for('{name}')"), type: 'const', system: yes, alias: alias,gsym: name)

	def c o = {}
		o:expression = no

		let body = node.body.c(o)

		let sheet = STACK.css
		let pre = Block.new([])
		pre.head = head

		pre.add(LIT(sheet.js(self,STACK)))

		let out = pre.c(o) + '\n/*body*/\n' + body

		if @helpers.len
			out = AST.cary(@helpers).join(';\n') + '\n' + out
		return out

export class ModuleScope < Scope

	def setup
		@selfless = no

	def namepath
		@node.namepath

export class ClassScope < Scope

	def setup
		@selfless = no

	def namepath
		@node.namepath

	# called for scopes that are not real scopes in js
	# must ensure that the local variables inside of the scopes do not
	# collide with variables in outer scopes -- rename if needed
	def virtualize
		# console.log "virtualizing ClassScope"
		var up = parent
		for own k,v of @varmap
			v.resolve(up,yes) # force new resolve
		self

	def prototype
		@prototype ||= ValueNode.new(OP('.',context,'prototype'))

export class TagScope < ClassScope

export class ClosureScope < Scope

export class FunctionScope < Scope

export class IsolatedFunctionScope < FunctionScope

	def lookup name
		@lookups ||= {}
		var ret = null
		name = AST.sym(name)
		if @varmap.hasOwnProperty(name)
			ret = @varmap[name]
		else
			ret = parent && parent.lookup(name)

			# only shadow variables inside the same closure?
			if ret and ret.closure == parent.closure
				@leaks ||= Map.new
				@nonlocals ||= {}
				@nonlocals[name] = ret

				# FIXME in the context of functional components,
				# self is to be considered a leaky variable
				# since it can change between renders

				let shadow = @leaks.get(ret)
				unless shadow
					@leaks.set(ret,shadow = ShadowedVariable.new(self,name,ret))
				ret = shadow
		ret

export class MethodScope < Scope

	def setup
		@selfless = no

	def isInExtend
		parent.isInExtend

	def visit
		super

		if STACK.tsc and isInExtend
			let cls = parent.closure.node
			let clsname = cls.@className
			if clsname
				let iface = null
				# Identifier should not have this much responsibility - it should be wrapped in an VarOrAccess?
				if clsname isa Identifier and !clsname.@variable
					iface = GLOBAL_INTERFACES[clsname.@value]

				let gen = clsname.option(:generics)
				let suptyp = clsname.c

				if gen
					suptyp += String(gen)

				let lit = node.option(:static) ? LIT("{cls.@className.c}") : (iface and iface:thistype ? LIT("null as any as {iface:thistype}") : LIT("this as (this & {suptyp})"))

				let ref = context.reference(lit)
				context.@useReference = yes
				ref.c
		self

export class FieldScope < Scope

	def setup
		@selfless = no

	def mergeScopeInto other
		for own k,v of @varmap
			continue if k == 'self'
			v.resolve(other,yes)
			other.declare(v)

		if @context and @context.@reference
			@context.@reference = other.context.reference
		yes

export class LambdaScope < Scope

	def context
		# why do we need to make sure it is referenced?
		unless @context
			@context = parent.context.fromScope(self)
		@context

export class FlowScope < Scope

	# these have no params themselves, refer to outer scopes -- hjmm
	def params
		@parent.params if @parent

	def register name, decl = null, o = {}
		if o:type != 'let' and o:type != 'const' and (closure != self)
			if var found = lookup(name)
				if found.type == 'let'
					# console.log "{name} already exists as a block-variable {decl}"
					decl.warn "Variable already exists in block" if decl

			closure.register(name,decl,o)
		else
			super(name,decl,o)

	# FIXME should override temporary as well

	def autodeclare variable
		# need to be unique for this
		parent.autodeclare(variable)

	def closure
		@parent.closure # this is important?

	def context
		@context ||= parent.context

	def closeScope
		# FIXME
		@context.reference if @context
		self

	def temporary refnode, o = {}, name = null
		(@systemscope or parent).temporary(refnode,o,name)

export class CatchScope < FlowScope

export class WhileScope < FlowScope

	def autodeclare variable
		vars.add(variable)

export class ForScope < FlowScope

	def autodeclare variable
		vars.add(variable)

export class IfScope < FlowScope

export class BlockScope < FlowScope

	def region
		node.region

export class TagBodyScope < FlowScope

# lives in scope -- really a node???
export class Variable < Node

	prop scope
	prop name
	prop alias
	prop type
	prop options
	prop initialized
	prop declared
	prop declarator
	prop autodeclare
	prop references
	prop export
	prop value
	prop datatype

	def pool
		null

	def initialize scope, name, decl, o
		@ref = STACK.@counter++
		@c = null
		@scope = scope
		@name  = name
		@alias = null
		@initialized    = yes
		@declarator  	= decl
		@autodeclare 	= no
		@declared		= o and o:declared || no
		@datatype 		= o and o:datatype
		@resolved		= no
		@options 		= o || {}
		@type			= o and o:type || 'var' # what about let here=
		@export			= no
		@references 	= [] # only needed when profiling
		@assignments 	= []
		self

	get _variable
		self

	def isImported
		@type == 'imported'

	def importPath
		isImported and @declarator ? @declarator.sourcePath : null

	get is_exported
		@declarator ? @declarator.option('export') : no

	get is_import
		isImported

	def typedAlias
		@typedAlias ||= Variable.new(@scope,@name + '$TYPED$',@declarator,@options)

	def isGlobal name
		@type == 'global' and (!name or @name == name)

	def closure
		@scope.closure

	def assignments
		@assignments

	def vartype
		@vartype or (@declarator and @declarator:datatype and @declarator.datatype)

	# Here we can collect lots of type-info about variables
	# and show warnings / give advice if variables are ambiguous etc
	def assigned val, source
		@assignments.push(val)
		if val isa Arr
			# just for testing really
			@isArray = yes
		else
			@isArray = no
		self

	def parents
		let parents = []
		let scope = closure.parent
		let res = self
		while scope and res and parents:length < 5
			if res = scope.lookup(@name)
				parents.unshift(res)
				let newscope = res.scope.parent
				if scope == newscope
					break
				scope = newscope

		return parents

	def resolve scope = scope, force = no
		return self if @resolved and !force

		@resolved = yes
		var closure = @scope.closure
		var item = @shadowing or scope.lookup(@name)

		# console.log "resolving var {@name} {scope}",scope == @closure,@virtual

		# if this is a let-definition inside a virtual scope we do need
		if @scope != closure and @type == 'let' and @virtual # or if it is a system-variable
			item = closure.lookup(@name)
			scope = closure

		if item == self
			scope.varmap[@name] = self
			return self

		elif item
			# possibly redefine this inside, use it only in this scope
			# if the item is defined in an outer scope - we reserve the
			if item.scope != scope && (options:let or @type == 'let')
				scope.varmap[@name] = self
				# if we allow native let we dont need to rewrite scope?
				return self if (!@virtual and !@shadowing)

			# different rules for different variables?
			if @options:proxy
				yes
			else
				var i = 0
				var orig = @name
				# it is the closure that we should use
				while scope.lookup(@name)
					@name = "{orig}{i += 1}"

		scope.varmap[@name] = self
		closure.varmap[@name] = self
		return self

	def reference
		self

	def node
		self

	def cache
		self

	def traverse
		self

	def free ref
		@declarator = null
		self

	def reuse ref
		@declarator = ref
		self

	def proxy par, index
		@proxy = [par,index]
		self

	def refcount
		@references:length

	def c params

		if params
			if params:as == 'declaration' and STACK.tsc
				if @datatype
					return c({}) + ":" + M(@datatype)
			
			if params:as == 'field'
				return "[" + c({}) + "]"

		return @c if @c

		if @typedAlias
			@typedAlias.c(params)
		# options - proxy??
		if @proxy
			if @proxy isa Node
				@c = @proxy.c
			else
				@c = @proxy[0].c
				if @proxy[1]
					@c += '[' + @proxy[1].c + ']'
		else
			resolve unless @resolved
			var v = (alias or name)
			@c = typeof v == 'string' ? helpers.toValidIdentifier(v) : v.c(as: 'variable')
			# allow certain reserved words
			# should warn on others though (!!!)
			# if @c == 'new'
			# 	@c = '_new'
			# 	# should happen at earlier stage to
			# 	# get around naming conventions
			@c = "{c}$" if RESERVED_REGEX.test(@c) # @c.match(/^(default)$/)
			# @c = @c + '/*' + @ref + '*/'
		return @c

	def js
		self.c()

	# variables should probably inherit from node(!)
	def consume node
		return self

	# this should only generate the accessors - not dael with references
	def accessor ref
		var node = LocalVarAccess.new(".",null,self)
		# this is just wrong .. should not be a regular accessor
		# @references.push([ref,el]) if ref # weird temp format
		return node

	def assignment val
		Assign.new('=',self,val)

	def addReference ref
		if ref isa Identifier
			ref.references(self)

		if ref:region and ref.region
			@references.push(ref)
			if ref.scope__ != @scope
				@noproxy = yes

		self

	def autodeclare
		return self if @declared
		@autodeclare = yes
		scope.autodeclare(self)
		@declared = yes
		self

	def predeclared
		@declared = yes
		self

	def toString
		String(name)

	def dump typ
		var name = name
		return null if name[0].match(/[A-Z]/)

		return {
			type: type
			name: name
			refs: AST.dump(@references, typ)
		}

	def via node
		ValueReferenceNode.new(self,node)

export class SystemVariable < Variable

	def pool
		@options:pool

	# weird name for this
	def predeclared
		scope.vars.remove(self)
		self

	def resolve
		return self if @resolved
		@resolved = yes
		let o = @options

		if o:gsym
			@name = "{o:gsym.replace(/\#/g,'$')}$"
			return self

		let ns = o:ns or ''

		let sysnr = (STACK.tsc or o:safe) ? @scope.incr('sysvar' + ns) : STACK.incr('sysvar' + ns)
		@name = "{ns}${sysnr}"
		return self

		# unless @name
		# adds a very random initial name
		# the auto-magical goes last, or at least, possibly reuse other names
		# "${Math.floor(Math.random * 1000)}"
		let o = @options

		var alias = o:alias or @name
		var typ = o:pool
		var names = [].concat(o:names)
		var alt = null
		var node = null

		@name = null

		let name = alias or InternalPrefixes.ANY

		if (/\d/).test(name[0])

			name = "_{name}"

		if (/\d$/).test(name)
			name = name + InternalPrefixes.SEP

		let nr = STACK.incr(name)
		nr = '' if nr == 1
		# if sysvar starts with a greek character (used for sysvars) - dont add dollar-sign
		if ReservedIdentifierRegex.test(name)
			@name = "{name}{nr}"
		else
			@name = "{name}φ{nr}"
		# @name = helpers.isSystemIdentifier(name) ? "{name}{nr}" : "{name}φ{nr}"
		# console.log "trying to set name??",name[0],name,@name
		return self

	def name
		resolve
		@name

export class ShadowedVariable < Variable
export class GlobalReference < Variable
export class PureReference < Variable

export class ZonedVariable < GlobalReference
	def forScope scope
		ZonedVariableAccess.new(self,scope)

	def c
		"{@name}"

export class DocumentReference < ZonedVariable
	def forScope scope
		self

	def c
		if STACK.isNode
			"{runtime:get_document}()"
		else
			"globalThis.document"

export class WindowReference < GlobalReference
	def c
		if STACK.isNode
			"{runtime:get_window}()"
		else
			"window"

export class ZonedVariableAccess < Node

	def initialize variable, scope
		@variable = variable
		@scope = scope

	def c
		let name = @variable.@name
		if STACK.isNode
			STACK.use("{name}")
			"{runtime:zone}.get('{name}',{@scope.context.c})"
		else
			# what if it is redefined somewhere?
			"{name}"

export class ImportProxy < Variable
	prop proxy
	prop path

	def initialize
		super
		@path = @options:path
		@exports = {}
		@touched = {}
		@head = LIT("import ")
		@head:c = self:head.bind(self)
		scope.@head.unshift(@head)
		var getter = do |t,p,r| self.access(p)
		@proxy_ = Proxy.new(self,{get: getter })

	def proxy
		@proxy_

	def touch key
		unless @touched[key]
			@touched[key] = access(key)
		self

	def head
		# for own key,value
		let keys = Object.keys(@exports)
		let touches = Object.values(@touched)
		let js = []
		let path = self.path

		if path == 'imba'
			path = STACK.imbaPath or 'imba'

		let pathjs = "'{path}'"

		if @importAll
			js.push("import * as {@name} from {pathjs};")

		if keys:length > 0
			let out = keys.map(do |a|
				let name = @exports[a].c
				name == a ? a : "{a} as {name}"
			).join(", ")

			js.push "import \{{out}\} from {pathjs};"

		if touches:length
			js.push "({touches.map(do $1.c + "()").join(",")});"

		return js:length ? js.join('\n') : ''

	def access key,ctx = null
		if @globalName
			return LIT("{M(@globalName,ctx)}.{C(key)}")
		let raw = C(key,mark: false)

		# what if this 
		# TODO what if there are collisions?
		@exports[raw] ||= ImportProxyAccess.new(@name ? "{@name}_{raw}" : raw)

	def c
		if !@importAll
			@importAll = yes
			# console.log "import all",STACK.current
			# STACK.current.warn("Referencing imba directly disables efficient tree-shaking")
		super

export class ImbaRuntime < ImportProxy

	def configure options
		if options:runtime == 'global' or STACK.tsc
			@globalName = 'imba'
		elif options:runtime
			self.path = options:runtime
		self

	def head
		return '' if STACK.tsc
		return super

	def c
		if !@importAll
			@importAll = yes
			# console.log "import all",STACK.current
			STACK.current.warn("Referencing imba directly disables efficient tree-shaking")
		@c = "imba"

	# def access key
	# 	# if @globalName
	# 	let raw = C(key,mark: false)
	# 	@exports[raw] ||= LIT("{@name}_{raw}")

export class ScopeContext < Node

	prop scope
	prop value
	prop reference

	def initialize scope, value
		@scope = scope
		@value = value
		@reference = null
		self

	def namepath
		@scope.namepath

	# instead of all these references we should probably
	# just register when it is accessed / looked up from
	# a deeper function-scope, and when it is, we should
	# register the variable in scope, and then start to
	# use that for further references. Might clean things
	# up for the cases where we have yet to decide the
	# name of the variable etc?

	def reference thisValue
		# if we are in  constructor we do want to declare it after super
		@reference ||= scope.lookup('self') or scope.declare("self",thisValue == undefined ? This.new : thisValue) # {@scope.@level}_

	def fromScope other
		IndirectScopeContext.new(other,self)

	def isConstant
		yes

	def c
		return reference.c if @useReference and @reference
		var val = @value # || @reference
		(val ? val.c : "this")

	def cache
		self

	def proto
		"{self.c}.prototype"

	def isGlobalContext
		no

export class IndirectScopeContext < ScopeContext

	def initialize scope, parent
		@scope = scope
		@parent = parent
		@reference = parent.reference

	def reference
		@reference # parent.reference

	def c
		reference.c

	def isGlobalContext
		@parent.isGlobalContext

export class RootScopeContext < ScopeContext

	def reference
		# should be a
		@reference ||= scope.lookup('global') # declare("self",scope.object, type: 'global')

	def c o
		# @reference ||= scope.declare("self",scope.object, type: 'global')
		# return "" if o and o:explicit
		return "globalThis"
		var val = reference # @value || @reference
		return (val and val != this) ? val.c : "this"
		# should be the other way around, no?
		# o and o:explicit ? super : ""

	def isGlobalContext
		yes

export class Super < Node

	prop member
	prop args

	def initialize keyword, member
		@keyword = keyword
		@member = member
		super

	def visit
		@method = STACK.method
		@up = STACK.parent
		if var m = STACK.method
			m.set(supr: {node: STACK.blockpart, block: STACK.block, real: self})
			m.set(injectInitAfter: STACK.blockpart)

		if @method # and @method.option('inExtension')
			@class = STACK.up(ClassDeclaration)
			
			if @class and !@method.isConstructor
				@class.set(calledSuper: yes)

		args.traverse if args	
		self

	def startLoc
		@keyword and @keyword.startLoc

	def endLoc
		@keyword and @keyword.endLoc

	def self.callOp name, params
		let op = OP('.',LIT('super'),name)
		CALL(op,params or [LIT('...arguments')])

	def c
		let m = @method
		let up = @up
		let sup = LIT('super')
		let op
		let top = option(:top)
		let virtual = m and m.option('inExtension')
		let args = self.args

		# need to know if our method is in the initial declaration
		if virtual and @class
			sup = CALL(STACK.corelib['sup$'],[slf,@class.refSym])
			# sup = CALL(@class.virtualSuper,[slf])

		# when super is written all by itself - it means to do the default action
		unless (up isa Access) or (up isa Call)
			if m and m.isConstructor and !member

				if STACK.tsc and @class and !@class.superclass
					return args ? "[{args.c}]" : ""

				let target = option(:target) or LIT('super')
				let fallbackArgs = option(:args) or [LIT('...arguments')]
				return M(CALL(target,args or fallbackArgs).c,@keyword)

			elif member
				op = OP('.',sup,member)
			elif m
				op = OP('.',sup,m.name)
				if m.isSetter
					op = OP('=',op,m.params.at(0))
				elif !m.isGetter
					args ||= [LIT('...arguments')]

			if args
				op = CALL(op,args)

			return op ? (M op.c(mark: false), @keyword) : '/**/'

		if member
			return OP('.',sup,member).c

		if up isa Call and m and !m.isConstructor
			return OP('.',sup,m.name).c

		return "super"

# constants

export var BR0 = Newline.new('\n')
export var BR = Newline.new('\n')
export var BR2 = Newline.new('\n\n')
export var SELF = Self.new
export var THIS = LIT('this')
export var PROTO = LIT('this.prototype')
# export var SUPER = Super.new

export var TRUE = True.new('true')
export var FALSE = False.new('false')
export var UNDEFINED = Undefined.new
export var NIL = Nil.new

export var ARGUMENTS = ArgsReference.new('arguments')
export var EMPTY = ''
export var NULL = 'null'

export var RESERVED = ['default','native','enum','with']
export var RESERVED_REGEX = /^(default|native|enum|with|new|char)$/
