You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
13195 lines
282 KiB
13195 lines
282 KiB
# 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 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'
|
|
|
|
var TAG_NAMES = constants.TAG_NAMES
|
|
var TAG_GLOBAL_ATTRIBUTES = constants.TAG_GLOBAL_ATTRIBUTES
|
|
|
|
var TAG_TYPES = {}
|
|
var TAG_ATTRS = {}
|
|
|
|
|
|
|
|
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'
|
|
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
|
|
|
|
# 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
|
|
|
|
|
|
|
|
# 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 '?.'
|
|
# if r isa VarOrAccess
|
|
# r = r.value
|
|
# # depends on the right side - this is wrong
|
|
# PropertyAccess.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 M = do |val,mark,o|
|
|
if mark == undefined
|
|
mark = val
|
|
|
|
if mark and mark:startLoc
|
|
val = C(val,o)
|
|
let ref = STACK.incr('sourcePair')
|
|
let start = mark.startLoc
|
|
let end = mark.endLoc
|
|
let m0 = ''
|
|
let m1 = ''
|
|
|
|
if start == 0 or start > 0
|
|
m0 = end >= start ? "/*%{start}|{ref}$*/" : "/*%{start}$*/"
|
|
|
|
if end == 0 or end > 0
|
|
m1 = start >= 0 ? "/*%{end}|{ref}$*/" : "/*%{end}$*/"
|
|
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 LIT = do |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 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
|
|
self
|
|
|
|
def runtime
|
|
@root.runtime
|
|
|
|
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'
|
|
AST.counterToShortRef(STACK.incr(ns))
|
|
|
|
def getSymbol ref, alias = null, name = ''
|
|
let key = ref or 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 toInternalClassName name
|
|
if name:toClassName
|
|
name = name.toClassName
|
|
elif name:c isa Function
|
|
name = name.c
|
|
|
|
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 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 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 == 'WEB' or key == 'BROWSER'
|
|
@meta:universal = no
|
|
return isWeb
|
|
elif key == 'NODE'
|
|
@meta:universal = no
|
|
return isNode
|
|
elif key == 'WORKER' or key == 'WEBWORKER'
|
|
@meta:universal = no
|
|
return isWorker
|
|
|
|
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 parent
|
|
@nodes[@nodes:length - 2]
|
|
|
|
def current
|
|
@nodes[@nodes:length - 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 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 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 oid
|
|
@oid ||= STACK.generateId('')
|
|
|
|
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 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)
|
|
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
|
|
|
|
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
|
|
@value?.startLoc
|
|
|
|
def endLoc
|
|
@value?.endLoc
|
|
|
|
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 js o
|
|
let op = @value
|
|
let out = []
|
|
|
|
if op isa Op and !(op isa Access)
|
|
let l = op.left
|
|
let r = op.right
|
|
|
|
out.push(l.cache.c(o))
|
|
out.push(helpers.singlequote(op.@op))
|
|
out.push(r.cache.c(o))
|
|
out = ["imba.$a=[{out.join(',')}]"]
|
|
out.push(op.c(o))
|
|
# op.cache
|
|
# out.push('imba.$asrt=undefined')
|
|
# out.push(op.c(o))
|
|
else
|
|
out.push('imba.$a=null')
|
|
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
|
|
|
|
self
|
|
|
|
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
|
|
if o and o:expression or v.match(/\n/) or @value.type == 'HERECOMMENT' # multiline?
|
|
var out = v.replace(/\*\//g, '\\*\\/').replace(/\/\*/g, '\\/\\*')
|
|
|
|
"/*{out}*/"
|
|
elif v.match(/\@(type|param)/)
|
|
"/** {v} */"
|
|
else
|
|
"// {v}"
|
|
|
|
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 cache o = {}
|
|
@value.cache(o)
|
|
|
|
def js o
|
|
@value.c
|
|
|
|
export class ListNode < Node
|
|
|
|
prop nodes
|
|
|
|
def initialize list
|
|
setup
|
|
@nodes = load(list or [])
|
|
@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)
|
|
let br = Token.new('TERMINATOR',post)
|
|
@nodes.unshift(Terminator.new(br))
|
|
a.@meta:post = ''
|
|
return self
|
|
|
|
class ClassInitBlock < Block
|
|
|
|
def c o
|
|
# @options ||= {}
|
|
# @options:braces = yes
|
|
let out = super
|
|
return 'static {\n' + helpers.indent(out) + '}'
|
|
|
|
# def js o, opts
|
|
# let out = super(o,opts)
|
|
# return 'static {\n' + out + '}'
|
|
|
|
class InstanceInitBlock < Block
|
|
|
|
class InstancePatchBlock < InstanceInitBlock
|
|
|
|
export class ClassField < Node
|
|
prop name
|
|
|
|
def initialize name
|
|
super
|
|
@name = name
|
|
|
|
def visit
|
|
@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
|
|
@vslot = osym('slot',String(@name))
|
|
@fslot = osym('meta')
|
|
@fname = @name.metaIdentifier
|
|
|
|
# let body = []
|
|
|
|
# let fn = @accessor = MethodDeclaration.new([],[LIT('"ets"'),CodeBlock.new([wrapper])],Identifier.new('render__'),null,{})
|
|
# let block = fn.body.add(CodeBlock.new([wrapper]))
|
|
# let desc = @accessor.scope.declare('desc',wrapper)
|
|
# console.log "wrapper {wrapper}"
|
|
# @accessor.body.add(Return.new(desc))
|
|
|
|
# if STACK.tsc
|
|
# body = [OP('=')]
|
|
# fn.traverse
|
|
# console.log fn.c
|
|
wrapper.@scope = @vscope = FieldScope.new(wrapper)
|
|
wrapper.@scope.@parent = scope__
|
|
wrapper.traverse
|
|
|
|
|
|
# @vslot = osym('meta')
|
|
# @getter = CALL(OP('.',THIS,@name),[])
|
|
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)
|
|
|
|
let up = STACK.current
|
|
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)
|
|
|
|
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 STACK.tsc
|
|
inner = "return {runtime:accessor}({wrapper.c(expression: yes)},{args},{metasym},{context})"
|
|
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 STACK.tsc
|
|
# console.log 'here',STACK.tsc
|
|
if wrapper
|
|
let setter = "{prefix}set {M(name,@name)}{self.setter.c(keyword: '')}"
|
|
let getter = "{prefix}get {M(name,@name)}{self.getter.c(keyword: '')}"
|
|
|
|
if datatype
|
|
getter = "{datatype.c} {getter}"
|
|
|
|
out = "{setter}\n{getter}\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
|
|
|
|
if self isa ClassAttribute or (@decorators and @decorators:length)
|
|
let val = value ? value.c : ''
|
|
let field = ''
|
|
let getval = 'null'
|
|
let setval = ''
|
|
let sym = osym
|
|
|
|
out = "{prefix} get {M(name,@name)}() \{ return /** @type any */({getval}) \}" # the value scope?
|
|
|
|
if datatype
|
|
out = "{datatype.c} {out}"
|
|
out += "\n{prefix} set {M(name,@name)}(val) \{ {setval} \}"
|
|
|
|
else
|
|
|
|
out = "{prefix}{M(name,@name)}" # the value scope?
|
|
out += " = {value.c};" if value
|
|
|
|
let typ = datatype
|
|
if typ
|
|
out = "{typ.c} {out}"
|
|
|
|
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 ""
|
|
let key = name
|
|
|
|
let tpl = """
|
|
Object.defineProperty(this.prototype,{key.c(as: 'value')},\{
|
|
enumerable: true,
|
|
set{self.setter.c(keyword: '')},
|
|
get{self.getter.c(keyword: '')}
|
|
\})
|
|
"""
|
|
|
|
return tpl
|
|
|
|
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
|
|
|
|
if up isa InstancePatchBlock
|
|
|
|
|
|
let rest = ctor.@params.at(restIndex,yes,'$$',LIT('{}'))
|
|
access = OP('.',rest,name)
|
|
access.cache(reuse: yes, name: 'v')
|
|
let right = OP('=',OP('.',THIS,name),access)
|
|
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
|
|
let rest = ctor.@params.at(restIndex,yes,'$$',LIT('null'))
|
|
access = OP('.',rest,name)
|
|
|
|
if value
|
|
access.cache(reuse: yes, name: 'v')
|
|
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
|
|
out = CALL(
|
|
OP('.',OP('.',THIS,@fname),STR('$init')),
|
|
[val,THIS,@vslot,LIT(@name.c(as: 'value'))]
|
|
)
|
|
|
|
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 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 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 = datatype.c + '(' + str + ')'
|
|
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)
|
|
console.log "PREBREAK"
|
|
@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 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 Self
|
|
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
|
|
|
|
# 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
|
|
if !params or params:as != 'declaration'
|
|
return "{@value.c}"
|
|
|
|
# include type??
|
|
if @defaults
|
|
return "{@value.c} = {@defaults.c}"
|
|
elif option(:splat)
|
|
return "..." + @value.c
|
|
else
|
|
return @value.c
|
|
|
|
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 = inline and param.datatype
|
|
if typ
|
|
part = typ.c + part
|
|
return part
|
|
)).join(",")
|
|
else
|
|
throw "not implemented paramlist js"
|
|
"ta" + AST.compact(map(|arg| arg.c )).join(",")
|
|
|
|
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 = variable.datatype
|
|
# 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
|
|
defs = "{typ.c}({defs})" if typ
|
|
"{variable.c} = {defs}"
|
|
elif typ
|
|
"{variable.c} = {typ.c}(undefined)"
|
|
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
|
|
if item.vartype
|
|
out += item.vartype.c + ' '
|
|
out += "{M(kind,keyword)} {item.c()};\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
|
|
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.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();\n{out}\n"
|
|
if script:sourceCode and script:sourceCode.match(/(^|[\r\n])\# @nocheck[\n\r]/)
|
|
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
|
|
|
|
# always create the sourcemap?
|
|
if o:sourcemap or STACK.tsc # should handle sourcemap no matter what?
|
|
let map = SourceMap.new(script,o).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 isExtension
|
|
option(:extension)
|
|
|
|
def isGlobal
|
|
option(:global)
|
|
|
|
def isNamespaced
|
|
@name isa Access
|
|
|
|
def exportForDts
|
|
return false unless STACK.tsc
|
|
return true if isNamespaced and (!@name.left.@variable or @name.left.@variable.isImported)
|
|
return false if isNamespaced and (@name.left.@variable and !@name.left.@variable.isImported)
|
|
return true if isGlobal and !option(:export)
|
|
return true if isExtension and (!@name.@variable or @name.@variable.isImported)
|
|
return false
|
|
# STACK.tsc and !!((isNamespaced and (!@name.left.@variable or @name.left.@variable.isImported)) or (isGlobal or isExtension)
|
|
|
|
def initialize name, superclass, body
|
|
# what about the namespace?
|
|
@traversed = no
|
|
if name isa VarOrAccess
|
|
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__') : SymbolIdentifier.new('#__init__'))
|
|
|
|
def patchKey
|
|
@patchKey ||= (STACK.tsc ? STACK.root.symbolRef('#__patch__') : SymbolIdentifier.new('#__patch__'))
|
|
|
|
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
|
|
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')) # (restIndex,yes,'$$',LIT('{}'))
|
|
fn.params.at(1,yes,'deep',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('{}'))
|
|
|
|
if @superclass
|
|
let call = Super.callOp(patchKey,[param])
|
|
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
|
|
# replace with some advanced lookup?
|
|
@body.@delimiter = ''
|
|
let blk = STACK.up(Block)
|
|
@decorators = blk and blk.collectDecorators
|
|
# console.log "FOUND DECORATORS FOR CLASS?",@decorators and @decorators:length
|
|
|
|
|
|
STACK.pop(self)
|
|
let sup = @superclass
|
|
|
|
@path = @name
|
|
@ownName = @name
|
|
@realName = @name isa Access ? @name.right : @name
|
|
|
|
|
|
# let dtsify = STACK.tsc and (isGlobal or isExtension or (isNamespaced and )
|
|
|
|
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'
|
|
if !STACK.tsc
|
|
sup = @superclass = null
|
|
else
|
|
@autosuper = yes
|
|
try
|
|
sup.@identifier.@symbol = 'ΤObject'
|
|
# sup.@value = 'AnyObject'
|
|
|
|
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 isGlobal and !isExtension and !isNamespaced and option(:export) and STACK.tsc
|
|
@exportName = STACK.toInternalClassName(@name)
|
|
|
|
if @ownName == @name and (exportForDts)
|
|
if isGlobal and !isExtension and !isNamespaced and option(:export)
|
|
@exportName = STACK.toInternalClassName(@name)
|
|
else
|
|
@ownName = STACK.toInternalClassName(@name)
|
|
|
|
# console.log 'visited class',exportForDts,String(@ownName),String(@exportName)
|
|
|
|
STACK.push(self)
|
|
ROOT.entities.add(namepath,self)
|
|
scope.visit
|
|
set(iife: STACK.up isa Instantiation)
|
|
var separateInitChain = true
|
|
var fields = []
|
|
var signature = []
|
|
var params = []
|
|
var declaredFields = {}
|
|
var restIndex = undefined
|
|
var instanceMethodMap = {}
|
|
|
|
for node in body
|
|
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)
|
|
|
|
body.traverse
|
|
|
|
var ctor = body.option(:ctor)
|
|
|
|
# if STACK.tsc
|
|
# if ctor and @autosuper
|
|
# ctor.body.add([LIT("super()"),BR],0)
|
|
# # return self
|
|
let tsc = STACK.tsc
|
|
var inits = InstanceInitBlock.new()
|
|
var staticInits = @staticInits = ClassInitBlock.new()
|
|
var patches = InstancePatchBlock.new()
|
|
|
|
|
|
if @realName
|
|
staticInits.add util.defineName(THIS,@realName.toStr),0
|
|
# OP('=',LIT('this[Symbol.for("#name")]'),@realName.toStr),0
|
|
|
|
var ctor = body.option(:ctor)
|
|
|
|
let fieldNodes = body.filter do |node| node isa ClassField
|
|
let allDecorators = []
|
|
|
|
for node in fieldNodes
|
|
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)
|
|
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
|
|
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])
|
|
|
|
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 sup = ctor.option(:supr)
|
|
if sup
|
|
sup: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
|
|
|
|
if !patches.isEmpty and !tsc
|
|
instancePatch
|
|
patches.set(ctor: instancePatch)
|
|
instancePatch.inject(patches)
|
|
|
|
if tsc and ctor and @autosuper
|
|
ctor.body.add([LIT("super()"),BR],0)
|
|
|
|
|
|
# staticInit.inject(staticInits,0)
|
|
|
|
if !STACK.tsc
|
|
let hasInitedHook = !!instanceMethodMap["#__inited__"]
|
|
let hasDecorators = allDecorators:length > 0
|
|
|
|
if hasDecorators
|
|
STACK.use('hooks')
|
|
let decosym = STACK.symbolFor('#__hooks__')
|
|
staticInits.unshift LIT("this.prototype[{decosym}] = {runtime:hooks}"), yes
|
|
# staticInit.inject LIT("this.prototype[{decosym}] = {runtime:hooks}"), 0
|
|
|
|
if !isTag and !ctor and (hasInitedHook or hasDecorators)
|
|
let ops = sup ? [Super.new(),BR] : [BR]
|
|
ctor = addMethod('constructor',[],ops,{})
|
|
|
|
if ctor and !isTag and !STACK.isStdLib
|
|
let ctorsym = STACK.symbolFor('#__initor__')
|
|
let initsym = STACK.symbolFor('#__inited__')
|
|
|
|
let hooksym = STACK.symbolFor('#__hooks__')
|
|
let initedHook = LIT("this[{hooksym}]&&this[{hooksym}].inited(this)")
|
|
|
|
# console.log "emitInited",initedHook,hasDecorators,hasInitedHook,STACK.imbaPath
|
|
|
|
|
|
if sup
|
|
let refsym = STACK.getSymbol()
|
|
staticInits.unshift LIT("this.prototype[{ctorsym}] = {refsym}"), 0
|
|
if hasInitedHook
|
|
ctor.inject LIT("if(this[{ctorsym}]==={refsym}) ({initedHook},this[{initsym}]());")
|
|
else
|
|
ctor.inject LIT("this[{ctorsym}]==={refsym} && ({initedHook},this[{initsym}] && this[{initsym}]())")
|
|
else
|
|
# if hasDecorators
|
|
# staticInit.inject LIT("this.prototype[{decosym}] = true"), 0
|
|
if hasInitedHook
|
|
ctor.inject LIT("if(!this[{ctorsym}]) ({initedHook},this[{initsym}]());")
|
|
elif hasDecorators
|
|
ctor.inject LIT("!this[{ctorsym}] && this[{hooksym}].inited(this);")
|
|
|
|
# If we are not in the stdlib / core - add an inherited hook for the class?
|
|
# ctor.body.add([callop,BR],0)
|
|
|
|
if !tsc and sup
|
|
# add a reference to the namespace as well?
|
|
staticInits.add util.inheritClass(THIS)
|
|
# staticInits.add LIT('Object.getPrototypeOf(this.prototype).constructor?.inherited?.(this)')
|
|
|
|
|
|
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 addGetter name, body
|
|
|
|
# def addSetter name, body
|
|
|
|
def js o
|
|
|
|
scope.virtualize # is this always needed?
|
|
scope.context.value = name
|
|
scope.context.reference = name
|
|
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 namespaced = name != cname
|
|
var initor = null
|
|
var sup = superclass
|
|
|
|
if typeof cname != 'string' and cname
|
|
cname = cname.c(mark: yes)
|
|
|
|
@cname = cname
|
|
|
|
# if origName
|
|
# staticInits.add LIT('this[Symbol.for("#name")] = "' + origName.c + '"'),0
|
|
|
|
var externalAccess = LIT(cname)
|
|
var supAccess = null
|
|
|
|
if STACK.tsc and isExtension and !exportForDts # (isNamespaced or @name.@variable and !@name.@variable.isImported)
|
|
let parts = []
|
|
let target = @name.c
|
|
for part in body.@nodes
|
|
parts.push(part.c(as: 'descriptor',target: target))
|
|
return parts.join(';\n')
|
|
let jsbody = body.c()
|
|
let jshead = M('class',keyword) # "{C(keyword,mark: yes)}"
|
|
|
|
|
|
if name
|
|
jshead += " {M cname, name}"
|
|
else
|
|
# console.log 'are we in an assignment?',"{STACK.up}"
|
|
if up isa VarReference
|
|
try jshead += " {up.@value.@symbol}"
|
|
|
|
if sup
|
|
supAccess = M(sup)
|
|
jshead += " extends {supAccess}"
|
|
|
|
|
|
if name isa Access and !exportForDts and !isExtension
|
|
# TODO or the definition is local
|
|
jshead = "{name.c} = {jshead}"
|
|
|
|
if option(:export) or (STACK.tsc and exportForDts)
|
|
# beware of double export
|
|
if option(:default)
|
|
jshead = "{M 'export',option(:export)} {M 'default',option(:default)} {jshead}"
|
|
else
|
|
jshead = "{M 'export',option(:export)} {jshead}"
|
|
|
|
let js = "{jshead} \{{jsbody}\}"
|
|
|
|
if isExtension and !STACK.tsc
|
|
let extTarget = self isa ExtendDeclaration ? LIT(@className.c) : LIT("{@className.c}.prototype")
|
|
|
|
if @virtualSuper
|
|
let wrap = OP('=',@virtualSuper,util.virtualSuper(extTarget))
|
|
extTarget = LIT("({wrap.c},{extTarget.c})")
|
|
# TODO can this happen in static block?
|
|
js += ";\n" + util.extend(extTarget,LIT("{externalAccess}.prototype")).c + ';\n'
|
|
|
|
if option(:global) and !STACK.tsc
|
|
# external access?
|
|
# js = "{js}; {scope__.root.globalRef}.{@cname} = {@cname}"
|
|
let access = name isa Access
|
|
let getter = name isa Access ? name.c : @cname
|
|
if STACK.tsc
|
|
js = "{js};{!access ? " export \{{@cname}\};" : ""} {scope__.root.globalRef}.{@cname} = {getter}"
|
|
else
|
|
js = "{js}; {scope__.root.globalRef}.{@cname} = {getter}"
|
|
|
|
# if option(:export) and option(:global) and STACK.tsc
|
|
|
|
if STACK.tsc and @exportName
|
|
js = "{js}; export \{{@ownName} as {@exportName}\}"
|
|
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
|
|
|
|
@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 className = name.toClassName
|
|
let sup = superclass
|
|
let anonGlobalTag = !option(:extension) and (!name.isClass) and STACK.tsc
|
|
|
|
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 STACK.tsc
|
|
sup = superclass ? superclass.toClassName : LIT('imba.Component')
|
|
if option(:extension)
|
|
# className = "Extended${className}"
|
|
# ought to support the nested stuff?
|
|
# let out = "class Ω{name.toExtensionName.replace(/\./g,'__')}Ω{STACK.incr('extend')}"
|
|
let out = "class {@mixinName.c}"
|
|
# extends {@className.c}
|
|
let obody = body.c
|
|
|
|
if STACK.tsc and false
|
|
# if the class-name is aliased via an import we want to include that here
|
|
# console.log 'cl',@className
|
|
obody = "\n__extends__= new {M(@className.c,@name)};\n{obody}"
|
|
|
|
out = "{out} \{{obody}\}"
|
|
out = "export {out}" unless @name.@variable
|
|
return out
|
|
else
|
|
body.unshift(LIT('static $$TAG$$\n'))
|
|
|
|
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)
|
|
|
|
let js = "(class \{{body.c}\}).prototype"
|
|
return util.extend("{cls.c}.prototype",LIT(js)).c
|
|
|
|
else
|
|
if name.isNative
|
|
name.error "tag {name.symbol} already exists"
|
|
|
|
let closure = scope__.parent
|
|
let jsbody = body.c()
|
|
let jshead = "{M 'class', keyword} {M className, name} extends {M sup, superclass}"
|
|
|
|
if option(:export)
|
|
if option(:default)
|
|
jshead = "{M 'export',option(:export)} {M 'default',option(:default)} {jshead}"
|
|
else
|
|
jshead = "{M 'export',option(:export)} {jshead}"
|
|
|
|
|
|
if anonGlobalTag and STACK.tsc # only tsc
|
|
# jshead = "globalThis[{M("'{M(name.toNodeName,name)} tag'",name)}] = {M 'class', keyword} extends {M sup, superclass}"
|
|
# jshead = "globalThis.{M className, name} = {M 'class', keyword} extends {M sup, superclass}"
|
|
jshead = "export {jshead}"
|
|
# jshead = "globalThis.{className} = {M 'class', keyword} {M className, name} extends {M sup, superclass}"
|
|
|
|
let js = "{jshead} \{{jsbody}\}"
|
|
|
|
@config:cssns = cssns if @cssns
|
|
@config:cssid = cssid if @cssid
|
|
|
|
if !STACK.tsc
|
|
if @staticInit
|
|
js += "; {OP('.',LIT(className),initKey).c}()"
|
|
# let ext = (option(:extends) or LIT('{}')).c
|
|
let ext = Obj.wrap(@config).c
|
|
if name.isClass
|
|
@config:name = name.symbol
|
|
js += "; {runtime:defineTag}({name.c},{className},{ext})"
|
|
|
|
# else
|
|
# if !option(:extension) and (!name.isClass)
|
|
# js += "; globalThis.{M name.toTscName, name} = {className};"
|
|
|
|
if STACK.tsc and (option(:global) or !name.isClass) and false
|
|
js += "; globalThis.{className} = {className}"
|
|
return js
|
|
|
|
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 returnType
|
|
datatype
|
|
|
|
def visit stack, o
|
|
# any function inside descriptors should compile as strong scopes
|
|
if stack.@descriptor and !stack.tsc
|
|
@scope = MethodScope.new(self) # (o and o:scope) || typ.new(self)
|
|
@scope.params = @params # = ParamList.new([])
|
|
|
|
scope.visit
|
|
|
|
@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
|
|
let o = []
|
|
# console.log 'found desc?',@desc
|
|
if @desc
|
|
@desc.@skip = yes
|
|
o.push(@desc.toString)
|
|
|
|
for item in @params.nodes when item isa Param
|
|
if item.datatype
|
|
o.push(item.jsdoc())
|
|
|
|
if option(:inExtension) and @target
|
|
let kls = @context.node
|
|
let name = @context.node.@className
|
|
if name and STACK.tsc
|
|
# console.log 'inext',@context.node.@className
|
|
# o.push('@this { this & ' + name.c + ' }')
|
|
# let thistype = "globalThis." + name.c
|
|
let thistype = name.c
|
|
if kls.option(:instanceOnly)
|
|
thistype = "typeof {thistype}"
|
|
else
|
|
thistype = "InstanceType<typeof {thistype}>"
|
|
# o.push('@this ' + thistype + '')
|
|
# FIXME not used?
|
|
|
|
|
|
if option(:jsdocthis)
|
|
|
|
o.push('@this ' + option(:jsdocthis) + '')
|
|
|
|
if returnType
|
|
o.push('@returns { ' + returnType.asRawType + ' }')
|
|
|
|
let doc = o.join('\n')
|
|
return (doc ? '/**\n' + doc + '\n*/\n' : '')
|
|
|
|
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)
|
|
|
|
# args = params.map do |par| par.name
|
|
# head = params.map do |par| par.c
|
|
# code = [head,body.c(expression: no)].AST.flatten.compact.join("\n").wrap
|
|
# FIXME creating the function-name this way is prone to create naming-collisions
|
|
# will need to wrap the value in a FunctionName which takes care of looking up scope
|
|
# and possibly dealing with it
|
|
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}) " + 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|
|
|
shadow.@proxy = @params.at(@params.count,yes)
|
|
@leaks.push(source)
|
|
self
|
|
|
|
export class Lambda < Func
|
|
def scopetype
|
|
var k = option(:keyword)
|
|
(k and k.@value == 'ƒ') ? (MethodScope) : (LambdaScope)
|
|
|
|
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 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
|
|
|
|
var o = @options
|
|
scope.visit
|
|
|
|
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
|
|
@target = closure.prototype
|
|
let inExt = closure.node.option('extension')
|
|
set(
|
|
prototype: @target,
|
|
inClassBody: yes,
|
|
inExtension: inExt
|
|
)
|
|
|
|
# if inExt
|
|
# scope.context.@value = LIT("/** @type {closure.node.@className}*/(this)")
|
|
|
|
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
|
|
|
|
if isConstructor and option(:supr)
|
|
let ref = scope__.context.@reference
|
|
let supr = option(:supr)
|
|
let node = supr:node
|
|
let block = supr:block
|
|
|
|
if ref
|
|
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
|
|
# 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 out = ""
|
|
|
|
if (option(:inClassBody) or option(:inObject)) and co:as != 'descriptor'
|
|
# what if this is async?
|
|
let prefix = ''
|
|
if self.isGetter
|
|
prefix = M('get',option(:keyword)) + ' '
|
|
elif self.isSetter
|
|
prefix = M('set',option(:keyword)) + ' '
|
|
# let prefix = self.isGetter() ? 'get ' : (self.isSetter() ? 'set ' : '')
|
|
|
|
prefix = "async {prefix}" if option(:async)
|
|
prefix = "{M('static',option(:static))} {prefix}" if option(:static)
|
|
out = "{prefix}{M name, null, as: 'field'}({params.c}){code}"
|
|
|
|
out = jsdoc() + out
|
|
# out = @params.jsdoc() + out
|
|
|
|
if option(:declareOnly) and !STACK.tsc
|
|
return ''
|
|
|
|
|
|
return out
|
|
|
|
var func = "({params.c})" + code
|
|
var ctx = context
|
|
var fname = helpers.toValidIdentifier(AST.sym(self.name))
|
|
|
|
|
|
if target
|
|
|
|
# special case here?
|
|
if STACK.tsc
|
|
# let o = option(:static) ? "typeof {target.c}"
|
|
# : "InstanceType<typeof {target.c}>"
|
|
set(jsdocthis: "typeof {target.c}")
|
|
|
|
# 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: {jsdoc()}{funcKeyword}{func}, configurable: true\})"
|
|
return out
|
|
elif isSetter
|
|
out = "Object.defineProperty({target.c},{fname},\{set: {jsdoc()}{funcKeyword}{func}, configurable: true\})"
|
|
return out
|
|
else
|
|
# if STACK.tsc
|
|
# let o = "InstanceType<typeof {target.c}>"
|
|
# set(jsdocthis: o)
|
|
|
|
let k = OP('.',target,@name)
|
|
out = "{k.c} = {funcKeyword} {func}"
|
|
|
|
if o:export
|
|
out = "exports.{o:default ? 'default' : fname} = {out}"
|
|
else
|
|
out = "{M funcKeyword, keyword} {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
|
|
M("null",@value)
|
|
|
|
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 == '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 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 = datatype.c + '(' + out + ')'
|
|
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.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)
|
|
if typ
|
|
out = "{typ.c}({out})"
|
|
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 == 'is'
|
|
@op = '==='
|
|
elif @op == 'isnt'
|
|
@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 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 startLoc
|
|
let l = @left
|
|
l and l:startLoc ? l.startLoc : super
|
|
|
|
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('!&',l,r.cache),
|
|
Parens.new([OP('|=',l,r),TRUE]),
|
|
FALSE
|
|
).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
|
|
|
|
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 == '||'
|
|
|
|
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
|
|
|
|
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 startLoc
|
|
let l = (@left or @op)
|
|
l and l:startLoc ? l.startLoc : @startLoc
|
|
|
|
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 o
|
|
# fix checks for String and Number
|
|
|
|
if right isa Identifier or right isa VarOrAccess
|
|
# WARN otherwise - what do we do? does not work with dynamic
|
|
# classes etc? Should probably send to utility function isa$
|
|
var name = AST.c(right.value)
|
|
var obj = left.node
|
|
# TODO also check for primitive-constructor
|
|
if name in ['String','Number','Boolean']
|
|
if STACK.tsc
|
|
return "(typeof {obj.c}=='{name.toLowerCase}')"
|
|
|
|
unless obj isa LocalVarAccess
|
|
obj.cache
|
|
# need a double check for these (cache left) - possibly
|
|
return "(typeof {obj.c}=='{name.toLowerCase}'||{obj.c} instanceof {name})"
|
|
|
|
# convert
|
|
var out = "{left.c} instanceof {right.c}"
|
|
|
|
# should this not happen in #c?
|
|
out = helpers.parenthesize(out) if o.parent isa Op
|
|
out
|
|
|
|
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 invert
|
|
@invert = !@invert
|
|
self
|
|
|
|
def js o
|
|
# var cond = @invert ? "== -1" : ">= 0"
|
|
# var idx = Util.indexOf(left,right)
|
|
var out = util.contains(left,right)
|
|
"{@invert ? '!' : ''}{out.c}"
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
def 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
|
|
|
|
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
|
|
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
|
|
|
|
# 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 = STACK.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 = option(:datatype)
|
|
|
|
if ctx
|
|
if self isa ImplicitAccess and typ and stack.tsc and !(up isa Block) and false
|
|
out = '/**@type{any}*/('+ctx.c+')' + out
|
|
else
|
|
out = ctx.c + out
|
|
|
|
if self isa ImplicitAccess
|
|
out = M(out,rgt.@token or rgt.@value)
|
|
|
|
if typ and (!(up isa Assign) or up.right.node == self)
|
|
if up isa Block and (self isa ImplicitAccess or lft isa Self)
|
|
out = typ.c + ' ' + out
|
|
else
|
|
out = typ.c + '(' + out + ')'
|
|
|
|
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
|
|
|
|
export class ImplicitAccess < Access
|
|
|
|
def datatype
|
|
super or @right.datatype
|
|
|
|
|
|
# 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)
|
|
|
|
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
|
|
@value = ImplicitAccess.new(".",(Self.new).traverse,@value).set(datatype: datatype)
|
|
return self
|
|
|
|
def js o
|
|
if @tagref
|
|
return @tagref.ref
|
|
|
|
let val = @variable or @value
|
|
if @variable and @variable.declarator != self
|
|
if STACK.tsc and val.@typedAlias
|
|
let out = val.@typedAlias.c
|
|
return out
|
|
# if @variable.c == 'val'
|
|
# # not when we are part of a setter though
|
|
# console.log 'compile varoraccess variable',@variable
|
|
let typ = datatype # or (STACK.tsc and @variable.vartype)
|
|
# FIXME not if it is in left-side of assignment
|
|
if typ
|
|
return typ.c() + '(' + @variable.c() + ')'
|
|
|
|
return val.c()
|
|
|
|
def node
|
|
@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)
|
|
|
|
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
|
|
@type = type and String(type)
|
|
@declared = yes # just testing now
|
|
|
|
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)
|
|
return self
|
|
|
|
def js stack,params
|
|
let out = @value.c()
|
|
let typ = (STACK.tsc and datatype)
|
|
# console.log "result of varreference {out}"
|
|
# console.log "is expression?? {@expression} {params:expression}"
|
|
if @right
|
|
let rgt = @right.c(expression: true)
|
|
if typ
|
|
rgt = "{typ.c}({rgt})"
|
|
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
|
|
if item.vartype
|
|
js += item.vartype.c + ' '
|
|
js += "{M(kind,@keyword)} {item.c()};\n"
|
|
|
|
if @value isa Obj
|
|
out = "({out})"
|
|
|
|
js += "{out}"
|
|
return js
|
|
|
|
out = "{@type} {out}"
|
|
if option(:export)
|
|
out = "{M('export',option(:export))} {out}"
|
|
|
|
if !@right and typ
|
|
out = typ.c + ' ' + 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)
|
|
|
|
|
|
# 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 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
|
|
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
|
|
|
|
|
|
|
|
if l isa Access and l.left isa Super and false
|
|
if let m = STACK.method
|
|
if m.option('inExtension')
|
|
let key = l.right
|
|
key = key.toStr if key isa Identifier
|
|
# return "{scope.__context.c}.super$set('{l.right.c}')"
|
|
let op = CALL(
|
|
OP('.',scope__.context,'super$set'),
|
|
[key,right]
|
|
)
|
|
return op.c(expression: true)
|
|
# We are setting self(!)
|
|
# TODO document functionality
|
|
|
|
# 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 l isa Access and l.@left.@value == 'OPS' and STACK.tsc
|
|
lc = "globalThis.{M(helpers.toNamespacedIdentifier('OPS',String(l.@right)),l.@right)}"
|
|
|
|
|
|
lc ||= l.c
|
|
var out = "{lc} {op} {right.c(expression: true)}"
|
|
|
|
if let typ = (datatype or (l and !(l isa VarReference) and l.datatype))
|
|
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
|
|
|
|
|
|
export class TypeAnnotation < Node
|
|
|
|
def initialize value
|
|
@value = value
|
|
self
|
|
|
|
def add item
|
|
@parts.push(item)
|
|
|
|
def startLoc
|
|
@value.startLoc + 1
|
|
|
|
def endLoc
|
|
@value.endLoc
|
|
|
|
def asParam name
|
|
"@param \{{asRawType}\} {name}"
|
|
|
|
def asRawType
|
|
let raw = String(@value).slice(1)
|
|
raw = raw.replace(/(^|[\[\,])\<([a-z\-\d]+)\>/g) do |m,pre,name|
|
|
# pre + "InstanceType<" + (TagTypeIdentifier.new(name)).toClassName + ">"
|
|
pre + (TagTypeIdentifier.new(name)).toClassName
|
|
|
|
raw = raw.replace(/(^|[\[\,])self([\]\,\)\>]|$)/g) do |m,pre,post|
|
|
pre + "this" + post
|
|
return M(raw,self)
|
|
|
|
def asIteratorValue
|
|
wrapDoc(asRawType + '[]')
|
|
|
|
def wrapDoc inner
|
|
return '/**@type {' + inner + '}*/'
|
|
|
|
def c
|
|
return '/**@type {' + asRawType + '}*/'
|
|
# M(String(@value).slice(1),self)
|
|
|
|
# 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
|
|
@value and @value:startLoc ? @value.startLoc : null
|
|
|
|
def endLoc
|
|
@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
|
|
return toStr.c if (up isa Util and !(up isa Util.Iterable)) # not all utils
|
|
let out = js
|
|
# FIXME should it not always enable?
|
|
if OPTS:sourcemap and (!o or o:mark !== false)
|
|
out = M(out, @token or @value)
|
|
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 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 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
|
|
@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 = @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)
|
|
if params
|
|
yes
|
|
# TODO - implement this
|
|
op = CALL(op,params)
|
|
# op = OP('=',op,params.@nodes[0])
|
|
elif value
|
|
op = OP('=',op,value)
|
|
else
|
|
op = OP('=',op,TRUE)
|
|
return op.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
|
|
@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
|
|
let part = DescriptorPart.new(KEY('configure'))
|
|
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 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
|
|
parts.add LIT("{ref.c}.default.literal = {@literal.c}")
|
|
|
|
if @callback
|
|
parts.add LIT("{ref.c}.callback = {@callback.c}")
|
|
|
|
parts.add(ref)
|
|
|
|
if STACK.tsc
|
|
return "((self,{ref.c}={val.c(mark: yes)})=>({parts.c}))(this)"
|
|
|
|
else
|
|
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
|
|
|
|
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 m
|
|
# console.log "called Call.m {@callee} ({startLoc} - {@callee.startLoc}) [{endLoc}]"
|
|
# super
|
|
|
|
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 == 'new'
|
|
console.log 'calling'
|
|
# if str == '__pure__'
|
|
# console.log 'PUREPRERE'
|
|
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)
|
|
|
|
@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 loc
|
|
@callee.loc
|
|
|
|
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
|
|
if STACK.tsc and false # (false or arg.raw == './ns')
|
|
yes
|
|
callee = callee.left
|
|
else
|
|
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)
|
|
# @args.@startLoc = option(:keyword).startLoc
|
|
# @args.@endLoc = option(:keyword).endLoc
|
|
self
|
|
|
|
|
|
def addBlock block
|
|
var pos = @args.filter(|n,i| n isa DoPlaceholder )[0]
|
|
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?
|
|
|
|
# if callee isa Call && callee.safechain
|
|
# yes
|
|
|
|
if callee isa Access
|
|
lft = callee.left
|
|
rgt = callee.right
|
|
|
|
if callee isa Super
|
|
if let m = STACK.method
|
|
# console.log "in method {m} {m.name} {m.option('inExtension')}"
|
|
if m.option('inExtension')
|
|
callee = OP('.',callee,m.name)
|
|
@receiver = scope__.context
|
|
# callee = @callee = OP('.',scope__.context,'super$')
|
|
# args.unshift(m.name.toStr) # = ArgList.new
|
|
# @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 rgt isa Identifier and rgt.value == 'assert' and !splat and false
|
|
let arg = args.first
|
|
arg.option(:assertion,yes)
|
|
args.@nodes[0] = AssertionNode.new(arg)
|
|
# console.log "Special assert call!! {arg.c}",arg isa Op
|
|
|
|
# rewrite a.len(..) to len$(a)
|
|
|
|
let safeop = ''
|
|
if callee isa Access and callee.op == '?.'
|
|
safeop = '?.'
|
|
# if callee.safechain
|
|
# console.log 'function safechain!!'
|
|
# var isfn = Util.IsFunction.new([callee])
|
|
# wrap = ["{isfn.c} && ",""]
|
|
# callee = OP('.',callee.left,callee.right)
|
|
# # callee should already be cached now -
|
|
|
|
# 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 startLoc
|
|
# keyword ? keyword.startLoc : super
|
|
|
|
def visit
|
|
keyword.warn 'Value.new is deprecated - use new Value'
|
|
super
|
|
|
|
def endLoc
|
|
keyword and keyword.endLoc or super
|
|
|
|
def startLoc
|
|
null
|
|
|
|
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
|
|
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 = STACK.tsc ? o:source : util.iterable(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 src = vars:source
|
|
var k = vars:key
|
|
var v = vars:value
|
|
var i = vars:index
|
|
|
|
var code
|
|
|
|
if options:own
|
|
# 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
|
|
# get all the variables declared in the let
|
|
for item in @declvars
|
|
if item.vartype
|
|
let vname = item.c
|
|
let decl = item.@declarator
|
|
let op = LIT("let {M item.typedAlias.c,decl} = {item.vartype.c}({vname})")
|
|
body.unshift(op)
|
|
|
|
|
|
# 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 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 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
|
|
"[{@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 ? "[{@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'
|
|
return asObjectKey
|
|
elif o:as == 'access'
|
|
yes
|
|
elif @single and @single isa Node
|
|
return @single.c(o)
|
|
else
|
|
asString
|
|
|
|
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 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 = '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}"
|
|
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
|
|
@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
|
|
val = val.body
|
|
|
|
# If the value is just a variable we can add it directly?
|
|
|
|
if val isa Access or val isa VarOrAccess
|
|
# let e = Token.new('IDENTIFIER','e')
|
|
let target = val
|
|
val = CALL(val,[LIT('e')])
|
|
val.@args.@startLoc = target.endLoc
|
|
val.@args.@endLoc = target.endLoc
|
|
# console.log 'is stack tsc?'
|
|
# 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 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 + '-' + oid
|
|
|
|
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,{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
|
|
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 cssflag
|
|
@cssflag ||= "{sourceId}"
|
|
|
|
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 = STACK.generateId('tag')
|
|
@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
|
|
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
|
|
@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 isFunctional
|
|
!!@options:functional
|
|
|
|
def isSVG
|
|
@isSVG ?= ((type isa TagTypeIdentifier and type.isSVG) or (@parent and @parent.isSVG))
|
|
|
|
def isAsset
|
|
@isAsset or no
|
|
|
|
def create_
|
|
if isFragment or isSlot
|
|
runtime:createLiveFragment
|
|
# LIT('imba.createLiveFragment')
|
|
elif isAsset
|
|
runtime:createAssetElement
|
|
|
|
elif isSVG
|
|
runtime:createSVGElement
|
|
# LIT('imba.createSVGElement')
|
|
elif isDynamicType
|
|
runtime:createDynamic
|
|
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 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 stack.tsc
|
|
# if tag is expression we want to suppress certain warnings
|
|
if type isa TagTypeIdentifier and !isSelf
|
|
if type.isAsset
|
|
add "{tvar} = new {M("SVGSVGElement",type)}"
|
|
elif type.isClass
|
|
add "{tvar} = new {M(type.toClassName,type)}"
|
|
else
|
|
# add "{tvar} = new globalThis.{M(type.toClassName,type)}"
|
|
add "{tvar} = new {M(type.toClassName,type)}"
|
|
elif isSelf
|
|
add "{tvar} = {type.c}"
|
|
elif isDynamicType
|
|
if @options:dynamic
|
|
add "{tvar} = new Γany"
|
|
add "{type.c}" # this is the string
|
|
else
|
|
add "{tvar} = new {M('Γany',type)}"
|
|
add "{type.c}" # this is the string
|
|
else
|
|
add "{tvar} = new {M('HTMLElement',type)}"
|
|
add "{type.c}" # this is the string
|
|
|
|
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 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
|
|
if @classNames.indexOf(orig) >= 0
|
|
@classNames.splice(@classNames.indexOf(orig),1)
|
|
@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'
|
|
]
|
|
|
|
# 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
|
|
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 !@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})) || ({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?
|
|
|
|
@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(dynamic: yes, detached: yes)
|
|
@consumed[0].@tvar = tvar
|
|
@consumed[0].@parent = parent
|
|
# 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})" # .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})"
|
|
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 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}))"
|
|
|
|
for part,i in val
|
|
unless part and part.isConstant # isa Literal
|
|
add "{vvar}[{i}]={part.c(o)}"
|
|
# add "({vvar}={rawVal.c(o)},{vvar}=={ref}.value || ({iref}={ref}.bind$('{item.key}',{mods},{bval}))"
|
|
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$'
|
|
cond = "(!{bvar}||{cond})" if isSelf or isDynamicType
|
|
|
|
if isDynamicType
|
|
add "({cond} && {tvar}.flags.reconcile({osym},{flagsToConcat.join("+' '+")}))"
|
|
else
|
|
add "({cond} && {tvar}.{meth}({flagsToConcat.join("+' '+")}))"
|
|
|
|
# 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})")
|
|
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})")
|
|
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 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 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 ownjs
|
|
var src = @source and @source.c()
|
|
# console.log("importing {@source:constructor} {src}")
|
|
|
|
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
|
|
|
|
if @specifiers and @source
|
|
"{M(keyword.c,keyword)} {AST.cary(@specifiers).join(',')} from {src}"
|
|
else
|
|
"{M(keyword.c,keyword)} {src}"
|
|
|
|
def js
|
|
let out = ownjs
|
|
return out
|
|
|
|
def push next
|
|
let curr = (@next or self)
|
|
@up.replace curr, [curr,BR,@next = next]
|
|
|
|
def visit
|
|
for item in @specifiers
|
|
item?.traverse()
|
|
|
|
scope__.@lastImport = self
|
|
@up = up
|
|
return
|
|
|
|
export class ImportTypeDeclaration < ESMDeclaration
|
|
|
|
def js
|
|
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
|
|
for item in @specifiers
|
|
item?.traverse()
|
|
self
|
|
|
|
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 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
|
|
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 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 = "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}-{oid}"
|
|
|
|
def visit stack, o
|
|
let cmp = @tagDeclaration = stack.up(TagDeclaration)
|
|
|
|
@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
|
|
# css nested inside tag tree
|
|
@tag = stack.up(TagLike)
|
|
@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
|
|
console.log "{ph}"
|
|
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
|
|
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 unless params
|
|
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
|
|
|
|
if theme[method] and !option(:plain)
|
|
let res = theme[method].apply(theme,@expr.toArray)
|
|
let expanded = []
|
|
|
|
if res isa Array
|
|
@expr = StyleExpressions.new(res)
|
|
elif res isa Object
|
|
# console.log 'theme has method',method,res
|
|
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)
|
|
|
|
if raw[0] == '#'
|
|
# console.log 'color property!!',raw
|
|
# raw = raw.slice(1)
|
|
@kind = 'color'
|
|
# 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 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'
|
|
|
|
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)
|
|
[name].concat(modifiers).join('§')
|
|
# @parts.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)
|
|
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
|
|
toString
|
|
|
|
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}_{oid}"
|
|
@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
|
|
self
|
|
|
|
def toString
|
|
c
|
|
|
|
def c o
|
|
|
|
let name = String(@name)
|
|
let pars = @params.c
|
|
let out = "{name}({pars})"
|
|
|
|
if (@property and @property.isColor)
|
|
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(/^([a-zA-Z]+\d+|black|white)$/)
|
|
color = "{raw}"
|
|
if self:param
|
|
color += "/" + self:param.toAlpha
|
|
super
|
|
|
|
def c o
|
|
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 c o
|
|
let raw = toRaw
|
|
let name = raw.slice(1)
|
|
let rich = Color.from(raw)
|
|
let a = self:param ? self:param.toAlpha : 1
|
|
|
|
if @property and @property.isColor
|
|
return rich.toVar
|
|
|
|
return "hsla({rich.toVar},{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'.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 toString
|
|
"{@number}{@unit or ''}"
|
|
|
|
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
|
|
return "calc(var(--u_{unit},1{unit}) * {@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")
|
|
console.log "slice {a} {b}"
|
|
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
|
|
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
|
|
scope__.root.helper(self,helper)
|
|
"{name}({@args.map(|v| v.c).join(',')})"
|
|
|
|
|
|
var HELPERS = {
|
|
setField: '''(target,key,value,o){
|
|
Object.defineProperty(target,key,{value:value});
|
|
};'''
|
|
|
|
unit: '''(value,unit){
|
|
return value + unit;
|
|
};'''
|
|
|
|
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;
|
|
};'''
|
|
|
|
inheritClass: '''(cls){
|
|
Object.getPrototypeOf(cls.prototype).constructor?.inherited?.(cls);
|
|
};'''
|
|
|
|
defineName: '''(cls,name){
|
|
Object.defineProperty(cls,"name",{value:name,configurable:true});
|
|
};'''
|
|
|
|
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: '''(decorators,target,key,desc){
|
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
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;
|
|
};'''
|
|
|
|
contains: '''(a,b){
|
|
var res = (b && b.indexOf) ? b.indexOf(a) : [].indexOf.call(a,b);
|
|
return res >= 0;
|
|
};'''
|
|
|
|
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
|
|
Util.Helper.new(args).set(name: k + '$__', helper: helper)
|
|
|
|
|
|
export class Util.Extend < Util
|
|
def helper
|
|
'''
|
|
function extend$__(target,ext){
|
|
// @ts-ignore
|
|
const descriptors = Object.getOwnPropertyDescriptors(ext);
|
|
delete descriptors.constructor;
|
|
// @ts-ignore
|
|
Object.defineProperties(target,descriptors);
|
|
return target;
|
|
};
|
|
'''
|
|
|
|
def js o
|
|
# When this is triggered, we need to add it to the top of file?
|
|
scope__.root.helper(self,helper)
|
|
"extend$__({AST.compact(AST.cary(args)).join(',')})"
|
|
|
|
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
|
|
scope__.root.helper(self,helper)
|
|
# When this is triggered, we need to add it to the top of file?
|
|
"idx$__({args.map(|v| v.c ).join(',')})"
|
|
|
|
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
|
|
scope__.root.helper(self,helper)
|
|
return "iter$__({args[0].c})"
|
|
|
|
export class Util.IsFunction < Util
|
|
|
|
def js o
|
|
"{args[0].c}"
|
|
|
|
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 register entity
|
|
# var path = entity.namepath
|
|
# @map[path] ||= entity
|
|
# self
|
|
|
|
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 stack
|
|
STACK
|
|
|
|
def kind
|
|
@kind ||= self:constructor:name.replace('Scope','').toLowerCase()
|
|
|
|
def initialize node, parent
|
|
@nr = STACK.incr('scopes')
|
|
@head = []
|
|
@node = node
|
|
@parent = parent
|
|
@vars = ScopeVariables.new([])
|
|
@entities = Entities.new(self)
|
|
@meta = {}
|
|
@annotations = []
|
|
@closure = self
|
|
@virtual = no
|
|
@counter = 0
|
|
@varmap = {}
|
|
@counters = {}
|
|
@varpool = []
|
|
@mixins = {}
|
|
@refcounter = 0
|
|
@declListeners = []
|
|
@level = (parent ? parent.@level : -1) + 1
|
|
setup
|
|
|
|
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 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}-{oid}"
|
|
|
|
def cssns
|
|
@cssns ||= "{root.sourceId}_{oid}"
|
|
|
|
def tagCache
|
|
@tagCache ||= declare('ϲτ',
|
|
LIT("{runtime:getRenderContext}()"),
|
|
system: yes
|
|
temporary: yes
|
|
alias: 'ϲτ'
|
|
)
|
|
|
|
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) # 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 head
|
|
[@vars,@params]
|
|
|
|
def c o = {}
|
|
o:expression = no
|
|
# need to fix this
|
|
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
|
|
|
|
register('global', self, type: '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')
|
|
|
|
# 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')
|
|
# @imba = register('imba', self, type: 'global', varclass: ImbaRuntime, path: 'imba')
|
|
@runtime = @imba.proxy
|
|
self
|
|
|
|
def importProxy name, path
|
|
@importProxies[name] ||= register("${name}$", 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 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
|
|
if cls.@className
|
|
let ref = context.reference(LIT("/** @type {cls.@className.c} */(/** @type unknown */(this))"))
|
|
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
|
|
|
|
def isImported
|
|
@type == 'imported'
|
|
|
|
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
|
|
console.log 'get parents!!!'
|
|
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 and 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 sysnr = STACK.incr('sysvar')
|
|
@name = "${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| "{a} as {@exports[a]}").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)
|
|
@exports[raw] ||= LIT("{@name}_{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)
|
|
|
|
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(@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)$/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|