Wip continued, sequence approach via compiling as calls, before switching to simple type checking

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -86,6 +86,9 @@
     ['brace', token in ['{', '}']]
     ['whitespace', /^\s+$/.test token]

+_isCollectionDelim = (atom) ->
+  atom.label in ['bracket', 'brace']
+
 typed = (a, b) ->
   b.type = a.type
   b
@@ -108,9 +111,9 @@
 crawl = (ast, cb, parent) ->
   if Array.isArray ast
     typed ast, (for node in ast
-      crawl node, cb)
-  else
-    cb ast, ast.token
+      crawl node, cb, ast)
+  else
+    cb ast, ast.token, parent

 crawlWhile = (ast, cond, cb) ->
   if Array.isArray ast
@@ -129,6 +132,12 @@
     cb ast
   ast

+visitExpressions = (expression, cb) ->
+  cb expression
+  if isForm expression
+    for term in _terms expression
+      visitExpressions term, cb
+
 inside = (node) -> node[1...-1]

 matchNode = (to, node) ->
@@ -159,36 +168,47 @@
     if token in tokens
       cb word

+mapCompile = (fn, string) ->
+  fn (new Context), astize tokenize string
+
+mapTyping = (fn, string) ->
+  ast = astize tokenize string
+  fn (ctx = new Context), ast
+  expressions = []
+  visitExpressions ast, (expression) ->
+    expressions.push [(collapse toHtml expression), printType expression.tea] unless expression is ast
+  types: values mapMap (__ printType, _type), subtractMaps ctx._scope(), builtInContext new Context
+  subs: values mapMap printType, ctx.substitution
+  ast: expressions
+
+_type = (declaration) ->
+  declaration.type
+
+mapSyntax = (fn, string) ->
+  ast = astize tokenize string
+  fn (new Context), ast
+  collapse toHtml ast
+
 class Context
-  constructor: (@macros = builtInMacros, @expand = {}, @names = [], @types = builtInContext(), @kinds = builtInKinds(), @nameIndex = 0) ->
-    for phase in ['syntax', 'typing', 'translation']
-      @expand[phase] = do (phase) => (expression) =>
-        log phase, expression
-        expand = @expand[phase]
-        if isCall expression
-          op = _operator expression
-          if isAtom op
-            if op.token of @macros
-              if phase is 'syntax'
-                op.label = 'keyword'
-              expanded = @macros[op.token][phase] this, expression
-              if expanded is expression
-                expression
-              else
-                expand expanded
-            else
-              callCompile[phase] this, expression
-          else
-            expandedOp = expand op
-            if expandedOp is op
-              callCompile[phase] this, expression
-            else
-              expand replicate expression,
-                (form_ (join [expandedOp], (_arguments expression)))
-        else
-          expression
+  constructor: ->
+    @_macros = builtInMacros
+    @expand = {}
+    @names = []
+    @variableIndex = 0
+    @typeVariabeIndex = 0
+    @substitution = newMap()
+    @statement = []
+    @cacheScopes = [[]]
+    @_assignTos = []
+    @scopes = [builtInContext this] # dangerous passing itself in constructor
+
+  macros: ->
+    @_macros

   _name: ->
+    (@names[@names.length - 1] ? {}).token
+
+  _nameExpression: ->
     @names[@names.length - 1]

   setName: (name) ->
@@ -197,128 +217,744 @@
   resetName: ->
     @names.pop()

-  kindOrName: (name) ->
-    (lookupInMap @kinds, name.token) or
-      (lookupInMap @types, name.token)
-
-topLevel =
-  syntax: (ctx, form) ->
-    for [name, def] in pairs _terms form
-      name.label = 'name'
-      if isAtom def
-        atomCompile.syntax def
+  setAssignTo: (compiled) ->
+    @_assignTos.push compiled
+
+  assignTo: ->
+    @_assignTos[@_assignTos.length - 1]
+
+  resetAssignTo: ->
+    @_assignTos.pop()
+
+  _scope: ->
+    @scopes[@scopes.length - 1]
+
+  declaration: (name) ->
+    @_declarationInScope @scopes.length - 1, name
+
+  _declarationInScope: (i, name) ->
+    (lookupInMap @scopes[i], name) or
+      i > 0 and (@_declarationInScope i - 1, name) or
+      throw "Could not find declaration for #{name}"
+
+  addType: (name, type) ->
+    if lookupInMap @_scope(), name
+      (lookupInMap @_scope(), name).type = type
+    else
+      addToMap @_scope(), name, {type}
+
+  addArity: (name, arity) ->
+    if lookupInMap @_scope(), name
+      (lookupInMap @_scope(), name).arity = arity
+    else
+      addToMap @_scope(), name, {arity}
+
+  addTypes: (names, types) ->
+    for name, i in names
+      @addType name, types[i]
+
+  type: (name) ->
+    (@declaration name).type
+
+  arity: (name) ->
+    (@declaration name).arity
+
+  newScope: ->
+    @scopes.push newMap()
+
+  closeScope: ->
+    @scopes.pop()
+
+  freshTypeVariable: (kind) ->
+    if not kind
+      throw new Error "Provide kind in freshTypeVariable"
+    new TypeVariable (freshName @typeVariabeIndex++), kind
+
+  extendSubstitution: (substitution) ->
+    @substitution = joinSubs substitution, @substitution
+
+  newJsVariable: ->
+    "i#{@variableIndex++}"
+
+  setGroupTranslation: ->
+    @cacheScopes.push []
+
+  cacheAssignTo: ->
+    if @assignTo() and not @_translationCache()
+      # Replace assign to with cache name
+      cacheName = @newJsVariable()
+      cache = [cacheName, @assignTo()]
+      @_assignTos[@_assignTos.length - 1] = cacheName
+      @cacheScopes[@cacheScopes.length - 1][0] = cache
+
+  _translationCache: ->
+    @cacheScopes[@cacheScopes.length - 1][0]
+
+  translationCache: ->
+    if cache = @cacheScopes.pop()[0]
+      [compileAssign cache]
+    else
+      []
+
+expressionCompile = (ctx, expression) ->
+  throw "invalid expressionCompile args" unless ctx instanceof Context and expression
+  rememberCompiled expression,
+    if isAtom expression
+      atomCompile ctx, expression
+    else if _isTuple expression
+      tupleCompile ctx, expression
+    else if _isSeq expression
+      seqCompile ctx, expression
+    else if isCall expression
+        op = _operator expression
+        if isName op
+          (if op.token of ctx.macros()
+            macroCompile
+          else
+            callCompile) ctx, expression
+        else
+          expandedOp = termCompile ctx, op
+          if isTranslated expandedOp
+            callUnknownTranslate ctx, expandedOp, expression
+          else
+            expressionCompile replicate expression,
+              (call_ (join [expandedOp], (_arguments expression)))
+    else
+      log "Not handled", expression
+      throw "Not handled expression in expressionCompile"
+
+rememberCompiled = (expression, result) ->
+  expression.compiled = result
+
+_compiled = (expression) ->
+  expression.compiled
+
+callCompile = (ctx, call) ->
+  operator = _operator call
+  args = _labeled _arguments call
+  labeledArgs = labeledToMap args
+  operator.label = 'operator'
+
+  return 'malformed' if tagFreeLabels args
+
+  paramNames = ctx.arity operator.token
+  if not paramNames
+    throw "unknown function #{operator.token}"
+  positionalParams = filter ((param) -> not (lookupInMap labeledArgs, param)), paramNames
+  nonLabeledArgs = map _snd, filter (([label, value]) -> not label), args
+
+  if nonLabeledArgs.length > positionalParams.length
+    malformed call, 'Too many arguments'
+  else
+    extraParamNames = positionalParams[nonLabeledArgs.length..]
+    extraParams = map token_, extraParamNames
+    positionalArgs = map id, nonLabeledArgs # copy
+    extraArgs = map id, extraParams
+    argsInOrder = (for param in paramNames
+      (lookupInMap labeledArgs, param) or
+        positionalArgs.shift() or
+        extraArgs.shift())
+    sortedCall = (call_ operator,  argsInOrder)
+
+    if ctx.assignTo()
+      if isCapital operator
+        compiled = callConstructorPattern ctx, sortedCall, extraParamNames
+        retrieve call, sortedCall
+        compiled
       else
-        ctx.expand.syntax def
-    form
-  typing: (ctx, form) ->
-    expanded = form_ concat (for [name, def] in pairs _terms form
-      if isAtom def
-        addToMap ctx.types, name.token, inferCtx ctx, def
-        typed = def
+        malformed call, "function patterns not supported"
+    else
+      if nonLabeledArgs.length < positionalParams.length
+        lambda = (fn_ extraParams, sortedCall)
+        compiled = macroCompile ctx, lambda
+        retrieve call, lambda
+        compiled
       else
-        ctx.setName name.token
-        typed = ctx.expand.typing def
-      [name, typed])
-    name.tea = lookupInMap ctx.types, name.token
+        compiled = callSaturatedKnownCompile ctx, sortedCall
+        retrieve call, sortedCall
+        compiled
+
+callConstructorPattern = (ctx, call, extraParamNames) ->
+  operator = _operator call
+  args = _arguments call
+  isExtra = (arg) -> (isAtom arg) && (_token arg) in extraParamNames
+  paramNames = ctx.arity operator.token
+
+  if args.length - extraParamNames.length > 1
+    ctx.cacheAssignTo()
+
+  compiledArgs = (for arg, i in args when not isExtra arg
+    ctx.setAssignTo "#{ctx.assignTo()}#{jsObjectAccess paramNames[i]}"
+    elemCompiled = expressionCompile ctx, arg
+    ctx.resetAssignTo()
+    elemCompiled)
+
+  for arg in args when isExtra arg
+    arg.tea = ctx.freshTypeVariable star
+
+  # Typing operator like inside a known call
+  operator.tea = freshInstance ctx, ctx.type operator.token
+
+  # Gets the general type as if the extra arguments were supplied
+  callTyping ctx, call
+
+  combinePatterns join [
+    (precs: [(cond_ "#{ctx.assignTo()} instanceof #{operator.token}")])], compiledArgs
+
+jsObjectAccess = (fieldName) ->
+  if (validIdentifier fieldName) is fieldName
+    ".#{fieldName}"
+  else
+    "['#{fieldName}']"
+
+callSaturatedKnownCompile = (ctx, call) ->
+  operator = _operator call
+  args = _arguments call
+
+  # TODO: instead of freshInstance here should be using type schemes
+  operator.tea = freshInstance ctx, ctx.type operator.token
+  # log "compiling saturated", operator, printType operator.tea
+  compiledArgs = termsCompile ctx, args
+  # log "call typing for #{collapse crawl call, _token}"
+  callTyping ctx, call
+
+  opName = validIdentifier operator.token
+  translatedOperator = if isCapital operator then "#{opName}.create" else opName
+  assignCompile ctx, call, "#{translatedOperator}(#{listOf compiledArgs})"
+
+labeledToMap = (pairs) ->
+  labelNaming = ([label, value]) -> [(_labelName label), value]
+  newMapKeysVals (unzip map labelNaming, filter all, pairs)...
+
+tagFreeLabels = (pairs) ->
+  freeLabels = filter (([label, value]) -> not value), pairs
+  # Free labels not supported now inside calls
+  freeLabels.map (label) -> label.label = 'malformed'
+  return freeLabels.length > 0
+
+callUnknownTranslate = (ctx, translatedOperator, call) ->
+  args = _arguments call
+  argList = listOf termsCompile ctx, args
+  assignCompile ctx, call, "_#{args.length}(#{translatedOperator}, #{argList})"
+
+callTyping = (ctx, call) ->
+  call.tea = callInfer ctx, _terms call
+
+callInfer = (ctx, terms) ->
+  # Curry the call
+  if terms.length > 2
+    [subTerms..., lastArg] = terms
+    callInferSingle ctx, (callInfer ctx, subTerms), lastArg.tea
+  else
+    [op, arg] = terms
+    callInferSingle ctx, op.tea, arg.tea
+
+callInferSingle = (ctx, operatorTea, argTea) ->
+  returnType = ctx.freshTypeVariable star
+  unify ctx, operatorTea, (typeFn argTea, returnType)
+  returnType
+
+termsCompile = (ctx, list) ->
+  termCompile ctx, term for term in list
+
+termCompile = (ctx, term) ->
+  ctx.setName()
+  compiled = expressionCompile ctx, term
+  ctx.resetName()
+  compiled
+
+expressionsCompile = (ctx, list) ->
+  expressionCompile ctx, expression for expression in list
+
+tupleCompile = (ctx, form) ->
+  elems = _terms form
+  arity = elems.length
+  if arity > 1
+    ctx.cacheAssignTo()
+
+  compiledElems =
+    if ctx.assignTo()
+      for elem, i in elems
+        ctx.setAssignTo "#{ctx.assignTo()}[#{i}]"
+        elemCompiled = expressionCompile ctx, elem
+        ctx.resetAssignTo()
+        elemCompiled
+    else
+      termsCompile ctx, elems
+  # TODO: could support partial tuple application via bare labels
+  #   map [0: "hello" 1:] {"world", "le mond", "svete"}
+  # TODO: should we support bare records?
+  #   [a: 2 b: 3]
+  form.tea = applyKindFn (tupleType arity), (tea for {tea} in elems)...
+
+  if ctx.assignTo()
+    combinePatterns compiledElems
+  else
+    form.label = 'operator'
+    assignCompile ctx, form, "[#{listOf compiledElems}]"
+
+seqCompile = (ctx, form) ->
+  elems = _terms form
+  size = elems.length
+  if size > 1
+    ctx.cacheAssignTo()
+
+  if sequence = ctx.assignTo()
+    hasSplat = no
+    requiredElems = 0
+    for elem in elems
+      if isSplat elem
+        hasSplat = yes
+      else
+        requiredElems++
+
+    if hasSplat and requiredElems is 0
+      return malformed 'Matching on sequences requires at least one element name', form
+
+    compiledArgs = (for elem, i in elems
+      rhs =
+        if isSplat elem
+          "seq_splat(#{i}, #{elems.length - i - 1}, #{sequence})"
+        else
+          "seq_at(#{i}, #{sequence})"
+      ctx.setAssignTo rhs
+      elemCompiled = expressionCompile ctx, elem
+      ctx.resetAssignTo()
+      elemCompiled)
+    cond = "seq_size(#{sequence}) #{if hasSplat then '>=' else '=='} #{requiredElems}"
+    combinePatterns join [(precs: [(cond_ cond)])], compiledArgs
+  else
+    # Compile as calls for type checking
+    # {a b c} to (& a (& b (& c)))
+    expressionCompile ctx, arrayToConses elems
+
+    form.label = 'operator'
+    elemType = if size > 0
+      elems[0].tea
+    else
+      freshTypeVariable star
+    form.tea = new TypeApp arrayType, elemType
+    assignCompile ctx, form, "[#{listOf map _compiled, elems}]"
+
+
+  # if size > 0
+  #   firstElemType = elems[0].tea
+  #   for {tea} in elems
+
+  # form.tea = applyKindFn (tupleType size), (tea for {tea} in elems)...
+
+arrayToConses = (elems) ->
+  if elems.length is 0
+    token_ 'empty-array'
+  else
+    [x, xs...] = elems
+    (call_ (token_ 'cons-array'), [x, (arrayToConses xs)])
+
+assignCompile = (ctx, expression, translatedExpression) ->
+  if to = ctx._nameExpression()
+
+    ctx.setGroupTranslation()
+    {precs, assigns} = patternCompile ctx, to, expression, translatedExpression
+
+    if assigns.length is 0
+      throw new "No assign in assignCompile"
+    listOfLines join ctx.translationCache(), map compileAssign, assigns
+  else
+    translatedExpression
+
+patternCompile = (ctx, pattern, matched, translatedMatched) ->
+  ctx.setAssignTo translatedMatched
+  # caching can occur while compiling the pattern
+  # precs are {cond}s and {cache}s, sorted in order they need to be executed
+  {precs, assigns} = expressionCompile ctx, pattern
+  ctx.resetAssignTo()
+
+  # Check the types
+  unify ctx, matched.tea, pattern.tea
+  # TODO: malformed "LHS\'s type doesn\'t match the RHS in assignment", pattern
+
+  precs: precs ? []
+  assigns: assigns ? []
+
+macroCompile = (ctx, call) ->
+  op = _operator call
+  op.label = 'keyword'
+  expanded = ctx.macros()[op.token] ctx, call
+  if not isWellformed expanded
+    'malformed'
+  else if isTranslated expanded
     expanded
-
-  translation: (ctx, form) ->
-    listOfLines (for [name, def] in pairs _terms form
-      if isAtom def
-        "var #{name.token} = #{atomCompile.translation def};"
+  else
+    expressionCompile ctx, expanded
+
+isTranslated = (result) ->
+  typeof result is 'string' or result instanceof String
+
+topLevel = (ctx, form) ->
+  listOfLines (for [name, def] in pairs _terms form
+    # for now just atom names
+    # syntaxNewName 'Name pattern required', name
+    ctx.setName name
+    compiled = expressionCompile ctx, def
+    ctx.resetName()
+    compiled)
+
+builtInMacros =
+
+  fn: (ctx, call) ->
+    # For now expect the curried constructor call
+    args = _arguments call
+    [paramList, defs...] = args
+    if not paramList or not _isTuple paramList
+      malformed call, 'Missing paramater list'
+      params = []
+    else
+      params = _terms paramList
+      map (syntaxNewName 'Parameter name expected'), params
+    defs ?= []
+    if defs.length is 0
+      malformed call, 'Missing function result'
+    else
+      [docs, defs] = partition isComment, defs
+      if defs.length % 2 == 0
+        [type, body, wheres...] = defs
       else
-        ctx.expand.translation def)
-
-builtInMacros =
-  data:
-    syntax: (ctx, form) ->
-      for term in _arguments form
-        if isAtom term
-          term.label = 'name'
-        else
-          for [name, type] in _labeled _terms term
-            type.label = 'typename'
-      form
-    typing: (ctx, form) -> #TODO: add to the type system the data
-      for [constr, params] in leftPairs isAtom, _arguments form
-        constrType = if params
-          join (_labeled _terms params).map(_snd).map(_token), [ctx._name()]
-        else
-          ctx._name()
-        addToMap ctx.types, constr.token, constrType
-        constr.tea = constrType
-      addToMap ctx.kinds, ctx._name(), ['*', '*']
-      form
-    translation: (ctx, form) ->
-      listOfLines (for [constr, params] in leftPairs isAtom, _arguments form
-        identifier = validIdentifier constr.token
-        paramNames = (_labeled _terms params or []).map(_fst).map(_labelName)
-          .map(validIdentifier)
-        paramList = paramNames.join(', ')
-        paramAssigns = blockOfLines paramNames.map (name) ->
-          "  this.#{name} = name;"
-        constrFn = """function #{identifier}(#{paramList}) {#{paramAssigns}};"""
-        constrValue = if params
-          """
-          #{identifier}.create = function(#{paramList}){
-            return new #{identifier}(#{paramList});
-          }"""
-        else
-          "#{identifier}.value = new #{identifier}();"
-        listOfLines [constrFn, constrValue]
-      )
-  record:
-    syntax: (ctx, form) ->
-      for [name, type] in _labeled _arguments form
-        type.label = 'typename'
-      form
-    typing: (ctx, form) ->
-      # (data #{ctx._name()} [#{_arguments form}])
-      replicate form,
-        (form_ [(token_ 'data'), (token_ ctx._name()), (tuple_ _arguments form)])
-
-inferCtx = (ctx, expression) ->
-  [s, nI, {tea}] = infer ctx.types, expression, ctx.nameIndex
-  tea
-
-atomCompile =
-  syntax: (atom) ->
-    {token} = atom
-    labelMapping atom,
-      ['numerical', /^~?\d+/.test token]
-      ['const', /^[A-Z][^\s]*$/.test token]
-      ['string', /^["']/.test token]
-      ['regex', /^\/[^ \/]/.test token]
-  translation: (atom) ->
-    {label, token} = atom
-    if label is 'numerical'
-      if token[0] is '-'
-        "(~ #{token})"
+        [body, wheres...] = defs
+      paramNames = map _token, params
+      # pattern
+      paramTypes = map (-> ctx.freshTypeVariable star), params
+      ctx.newScope()
+      # log "adding types", (map _token, params), paramTypes
+      ctx.addTypes (map _token, params), paramTypes
+      # log "types added"
+      # log "compiling", body
+      compiledBody = termCompile ctx, body
+      # log "compiled", body.tea
+      ctx.closeScope()
+      call.tea = typeFn paramTypes..., body.tea
+
+      # Syntax
+      isUsedParam = (expression) ->
+        (isName expression) and (_token expression) in paramNames
+      map (syntaxNameAs '', 'param'), filterAst isUsedParam, body
+
+      # Arity
+      if ctx._name()
+        ctx.addArity ctx._name(), paramNames
+
+      assignCompile ctx, call,
+        """λ(function (#{listOf paramNames}) {
+          return #{compiledBody};
+        }"""
+  # data
+  #   listing or
+  #     pair
+  #       constructor-name
+  #       record
+  #         type
+  #     constant-name
+  data: (ctx, call) ->
+    defs = pairsLeft isAtom, _arguments call
+    # Syntax
+    syntaxNewName 'Name required to declare new algebraic data', ctx._nameExpression()
+    [names, typeArgLists] = unzip defs
+    map (syntaxNewName 'Type constructor name required'), names
+    for typeArgs in typeArgLists
+      if isRecord typeArgs
+        for type in _snd unzip _labeled _terms typeArgs
+          syntaxType type
       else
-        token
-    else if label is 'const'
+        typeArgs.label = 'malformed'
+    # Types
+    for [constr, params] in defs
+      constrType = desiplifyType if params
+        join (_labeled _terms params).map(_snd).map(_token), [ctx._name()]
+      else
+        ctx._name()
+      ctx.addType constr.token, constrType
+      constr.tea = constrType
+    # TODO: support polymorphic data
+    # We don't add binding to types, but maybe need to store elsewhere
+    # ctx.addType ctx._name(), typeConstant ctx._name()
+    # Arity
+    for [constr, params] in defs when params
+      addArity constr.token,
+        (_labeled _terms params).map(_fst).map(_labelName)
+    # Translate
+    listOfLines (for [constr, params] in defs
+      identifier = validIdentifier constr.token
+      paramNames = (_labeled _terms params or []).map(_fst).map(_labelName)
+        .map(validIdentifier)
+      paramList = paramNames.join(', ')
+      paramAssigns = blockOfLines paramNames.map (name) ->
+        "  this.#{name} = name;"
+      constrFn = """function #{identifier}(#{paramList}) {#{paramAssigns}};"""
+      constrValue = if params
+        """
+        #{identifier}.create = λ(function(#{paramList}){
+          return new #{identifier}(#{paramList});
+        });"""
+      else
+        "#{identifier}.value = new #{identifier}();"
+      listOfLines [constrFn, constrValue])
+
+  record: (ctx, call) ->
+    args = _arguments call
+    for [name, type] in _labeled args
+      if not name
+        malformed type, 'Label is required'
+      if not type
+        malformed name, 'Missing type'
+      if name and type
+        syntaxType type
+    if args.length is 0
+      malformed call, 'Missing arguments'
+    # TS: (data #{ctx._name()} [#{_arguments form}])
+    replicate call,
+      (call_ (token_ 'data'), [(token_ ctx._name()), (tuple_ args)])
+
+  # match
+  #   subject
+  #   listing of
+  #     pair
+  #       pattern
+  #       result
+  match: (ctx, call) ->
+    [subject, cases...] = _arguments call
+    varNames = []
+    if not subject
+      return malformed 'match `subject` missing', call
+    if cases.length % 2 != 0
+      return malformed 'match missing result for last pattern', call
+    subjectCompiled = termCompile ctx, subject
+
+    # To make sure all results have the same type
+    call.tea = resultType = ctx.freshTypeVariable star
+
+    ctx.setGroupTranslation()
+    compiledCases = conditional (for [pattern, result] in pairs cases
+
+      ctx.newScope() # for variables defined inside pattern
+
+      {precs, assigns} = patternCompile ctx, pattern, subject, subjectCompiled
+
+      # Compile the result, given current scope
+      compiledResult = termCompile ctx, result #compileImpl result, furtherHoistable
+      unify ctx, resultType, result.tea
+      ctx.closeScope()
+
+      varNames.push (findDeclarables precs)...
+
+      matchBranchTranslate precs, assigns, compiledResult
+    ), "throw new Error('match failed to match');" #TODO: what subject?
+    assignCompile ctx, call, iife listOfLines concat (filter _is, [
+      ctx.translationCache()
+      varList varNames
+      compiledCases])
+
+# Creates the condition and body of a branch inside match macro
+matchBranchTranslate = (precs, assigns, compiledResult) ->
+  {conds, preassigns} = constructCond precs
+  [hoistedWheres, furtherHoistable] = hoistWheres [], assigns #hoistWheres hoistableWheres, assigns
+
+  [conds, indentLines '  ',
+    concat [
+      (map compileAssign, (join preassigns, assigns))
+      hoistedWheres.map(compileDef)
+      ["return #{compiledResult};"]]]
+
+iife = (body) ->
+  """(function(){
+      #{body}}())"""
+
+varList = (varNames) ->
+  if varNames.length > 0 then "var #{listOf varNames};" else null
+
+conditional = (condCasePairs, elseCase) ->
+  if condCasePairs.length is 1
+    [[cond, branch]] = condCasePairs
+    if cond is 'true'
+      return branch
+  ((for [cond, branch], i in condCasePairs
+    control = if i is 0 then 'if' else ' else if'
+    """#{control} (#{cond}) {
+        #{branch}
+      }""").join '') + """ else {
+        #{elseCase}
+      }"""
+
+# From precs, find caches and the LHS are declarable variables
+findDeclarables = (precs) ->
+  map (__ _fst, _cache), (filter _cache, precs)
+
+combinePatterns = (list) ->
+  precs: concat map _precs, filter _precs, list
+  assigns: concat map _assigns, filter _assigns, list
+
+_precs = ({precs}) ->
+  precs
+
+_assigns = ({assigns}) ->
+  assigns
+
+_cache = ({cache}) ->
+  cache
+
+cache_ = (x) ->
+  cache: x
+
+cond_ = (x) ->
+  cond: x
+
+malformed = (expression, message) ->
+  # TODO support multiple malformations
+  expression.malformed = message
+  'malformed'
+
+isWellformed = (expression) ->
+  if expression.malformed
+    no
+  else
+    if isForm expression
+      for term in _terms expression
+        unless isWellformed term
+          return no
+    yes
+
+atomCompile = (ctx, atom) ->
+  {token} = atom
+  # Syntax
+  atom.label = regexMapping token,
+    ['numerical', /^~?\d+/]
+    ['const', /^[A-Z][^\s]*$/]
+    ['string', /^["]/]
+    ['char', /^[']/]
+    ['regex', /^\/[^ \/]/]
+  {label} = atom
+  # Typing and Translation
+  {type, translation, pattern} =
+    switch label
+      when 'numerical'
+        numericalCompile ctx, token
+      when 'regex'
+        regexCompile ctx, token
+      when 'char'
+        type: typeConstant 'Char'
+        translation: token
+        pattern: literalPattern ctx, token
+      when 'string'
+        type: typeConstant 'String'
+        translation: token
+        pattern: literalPattern ctx, token
+      when 'const'
+        constCompile ctx, token
+      else
+        nameCompile ctx, atom, token
+  atom.tea = type
+  if ctx.assignTo()
+    pattern
+  else
+    assignCompile ctx, atom, translation
+
+nameCompile = (ctx, atom, token) ->
+  if ctx.assignTo()
+    ctx.addType token, (type = ctx.freshTypeVariable star)
+    atom.label = 'name'
+    type: type
+    pattern:
+      assigns:
+        [[(validIdentifier token), ctx.assignTo()]]
+  else
+    type: ctx.type token
+    translation: validIdentifier token
+
+constCompile = (ctx, token) ->
+  type = ctx.type token
+  exp = ctx.assignTo()
+  if exp
+    type: type
+    pattern:
+      precs: [_cond
+        switch token
+          when 'True' then "#{exp}"
+          when 'False' then "!#{exp}"
+          else
+            "#{exp} instanceof #{token}"]
+  else
+    type: type
+    translation:
       switch token
         when 'True' then 'true'
         when 'False' then 'false'
-        else "#{token}.value"
-    else if label in ['regex', 'string']
-      token
-    else
-      validIdentifier token
-
-callCompile =
-  syntax: (ctx, call) ->
-    op = _operator call
-    if isAtom op
-      op.label = 'operator'
-    for arg in _arguments call
-      ctx.expand.syntax arg
-    call
-
-form_ = (list) ->
+        else
+          field = if isConstructor ctx.type token then 'create' else 'value'
+          "#{validIdentifier token}.#{field}"
+
+numericalCompile = (ctx, token) ->
+  translation = if token[0] is '~' then "(-#{token})" else token
+  type: typeConstant 'Num'
+  translation: translation
+  pattern: literalPattern ctx, translation
+
+regexCompile = (ctx, token) ->
+  type: typeConstant 'Regex'
+  translation: token
+  pattern:
+    if ctx.assignTo()
+      precs: [cond_ "#{ctx.assignTo()}.string" + " === #{token}.string"]
+
+literalPattern = (ctx, translation) ->
+  if ctx.assignTo()
+    precs: [cond_ "#{ctx.assignTo()}" + " === #{translation}"]
+
+regexMapping = (token, regexes...) ->
+  for [label, regex] in regexes
+    if regex.test token
+      return label
+
+# type expressions syntax
+# or
+#   atom -> or
+#     type constant
+#     type variable
+#     partial type class (but we don't know that in syntax phase - get rid of phases?)
+#   form -> or
+#     call
+#       type constructor
+#       types
+#     tuple
+#       type
+#     call (partial type class call)
+#       type class
+#       types
+syntaxType = (expression) ->
+  # ignore type classes for now
+  if isName expression
+    expression.label = if isCapital then 'typename' else 'typevar'
+  else if isTuple expression
+    map syntaxType, (_terms expression)
+  else if isCall expression
+    syntaxNameAs 'Constructor name required', 'typecons', (_operator expression)
+    map syntaxType, (_arguments expression)
+
+syntaxNewName = (message, atom) ->
+  curried = (atom) ->
+    syntaxNameAs message, 'name', atom
+  if atom then curried atom else curried
+
+syntaxNameAs = (message, label, atom) ->
+  curried = (atom) ->
+    if isName atom
+      atom.label = label
+    else
+      malformed atom, message
+  if atom then curried atom else curried
+
+call_ = (op, args) ->
   concat [
     tokenize '('
-    list
+    [op]
+    args
     tokenize ')'
   ]

@@ -329,6 +965,9 @@
     tokenize ']'
   ]

+fn_ = (params, body) ->
+  (call_ (token_ 'fn'), [(tuple_ params), body])
+
 token_ = (string) ->
   (tokenize string)[0]

@@ -341,9 +980,32 @@
 listOfLines = (lines) ->
   lines.join '\n'

+indentLines = (indent, lines) ->
+  blockOfLines map ((line) -> indent + line), (filter _notEmpty, lines)
+
+listOf = (args) ->
+  args.join ', '
+
+isComment = (expression) ->
+  (isCall expression) and ('#' is _token _operator expression)
+
 isCall = (expression) ->
-  (Array.isArray expression) and expression.length > 2 and
+  (isForm expression) and (isEmptyForm expression) and
     expression[0].label is 'paren'
+
+isRecord = (expression) ->
+  if _isTuple expression
+    [labels, values] = unzip pairs _terms expression
+    labels.length is values.length and (allMap isLabel, labels)
+
+_isSeq = (expression) ->
+  (isForm expression) and expression[0].label is 'brace'
+
+_isTuple = (expression) ->
+  (isForm expression) and expression[0].label is 'bracket'
+
+isEmptyForm = (form) ->
+  (_terms form).length > 0

 isForm = (expression) ->
   Array.isArray expression
@@ -351,35 +1013,61 @@
 isLabel = (atom) ->
   /:$/.test atom.token

+isCapital = (atom) ->
+  /[A-Z]/.test atom.token
+
+isName = (expression) ->
+  throw "Nothing passed to isName" unless expression
+  (isAtom expression) and /[^~"'\/].*/.test expression.token
+
 isAtom = (expression) ->
   not (Array.isArray expression)

-# Returns tuples with all arguments, possibly with associated label
-_labeled = (terms) ->
-  rightPairs isLabel, terms
-
-rightPairs = (keyTest, list) ->
+_labeled = (list) ->
+  pairsLeft isLabel, list
+
+pairsLeft = (leftTest, list) ->
   listToPairsWith list, (item, next) ->
-    if next and not keyTest next
-      if item and keyTest item
-        [item, next]
-      else if next
-        [null, next]
-
-leftPairs = (keyTest, list) ->
-  listToPairsWith list, (item, next) ->
-    if item and keyTest item
-      if next and not keyTest next
-        [item, next]
+    if leftTest item
+      [item, (if next and not leftTest next then next else null)]
+    else
+      if leftTest item
+        [item, null]
       else
-        [item, null]
+        [null, item]
+
+pairsRight = (rightTest, list) ->
+  pairsLeft ((x) -> not rightTest x), list
+  ###listToPairsWith list, (item, next) ->
+    if next and rightTest next
+      [(if not rightTest item then item else null), next]
+    else
+      if rightTest item
+        [null, item]
+      else
+        [item, null]###

 listToPairsWith = (list, convertBy) ->
-  (convertBy list[i], list[i + 1] for i in [-1..list.length]).filter _is
+  filter _is, (i = 0; while i < list.length
+    result = convertBy list[i], list[i + 1]
+    if result[0] and result[1]
+      i++
+    i++
+    result)
+
+unzip = (pairs) ->
+  [
+    filter _is, map _fst, pairs
+    filter _is, map _snd, pairs
+  ]

 replicate = (expression, newForm) ->
   newForm

+retrieve = (expression, newForm) ->
+  expression.tea = newForm.tea
+  expression.malformed = newForm.malformed
+
 _operator = (call) ->
   (_terms call)[0]

@@ -387,7 +1075,7 @@
   (_terms call)[1..]

 _terms = (form) ->
-  form[1...-1].filter ({token}) -> not (token and token.match /^\s+$/)
+  form[1...-1].filter ({label}) -> label isnt 'whitespace'

 _snd = ([a, b]) -> b

@@ -396,19 +1084,53 @@
 _labelName = (atom) -> (_token atom)[0...-1]

 _token = ({token}) -> token
+
+filterAst = (test, expression) ->
+  join (filter test, [expression]),
+    if isForm expression
+      concat (filterAst test, term for term in _terms expression)
+    else
+      []

 join = (seq1, seq2) ->
   seq1.concat seq2

+concatMap = (fn, list) ->
+  concat map fn, list
+
 concat = (lists) ->
   [].concat lists...

+id = (x) -> x
+
+map = (fn, list) ->
+  if list then list.map fn else (list) -> map fn, list
+
+allMap = (fn, list) ->
+  all (map fn, list)
+
+all = (list) ->
+  (filter _is, list).length is list.length
+
+filter = (fn, list) ->
+  list.filter fn
+
+partition = (fn, list) ->
+  [(filter fn, list), (filter ((x) -> not (fn x)), list)]
+
+_notEmpty = (x) -> x.length > 0
+
 _is = (x) -> !!x
+
+__ = (fna, fnb) ->
+  (x) -> fna fnb x

 theme =
   keyword: 'red'
   numerical: '#FEDF6B'
-  typename: '#9C49B6'
+  const: '#FEDF6B'
+  typename: '#FEDF6B'
+  typecons: '#67B3DD'
   label: '#9C49B6'
   string: '#FEDF6B'
   paren: '#444'
@@ -660,8 +1382,14 @@
   apply ast, typifyMost, mainLabeling, labelOperators

 toHtml = (highlighted) ->
-  crawl highlighted, (word) ->
-    (word.ws or '') + colorize(theme[word.label ? 'normal'], word.token)
+  crawl highlighted, (word, token, parent) ->
+    (word.ws or '') + colorize(theme[labelOf word, parent], token)
+
+labelOf = (word, parent) ->
+  if (_isCollectionDelim word) and parent
+    parent.label
+  else
+    word.label or 'normal'

 apply = (onto, fns...) ->
   result = onto
@@ -1081,6 +1809,7 @@
 sortTopologically = ([graph, dependencies]) ->
   reversedDependencies = reverseGraph graph
   independent = []
+  console.log graph, dependencies

   for node in graph
     node.origSet = cloneSet node.set
@@ -1100,6 +1829,8 @@
       removeFromSet child.set, name for name in finishedParent.names
       moveToIndependent child if child.set.size is 0

+
+  console.log "done", sorted, dependencies
   for node in sorted
     node.set = node.origSet

@@ -1179,14 +1910,20 @@
   # Constants
   (pattern) ->
     trigger: pattern.label is 'const'
-    cond: (exp) -> ["'#{pattern.token}' in #{exp}"]
+    cond: (exp) ->
+      [switch pattern.token
+        when 'True' then "#{exp}"
+        when 'False' then "!#{exp}"
+        else
+          "#{exp} instanceof #{pattern.token}"]
   # Name
   (pattern) ->
-    trigger: pattern.label is 'name'
+    trigger: isName pattern
     assignTo: (exp) ->
       name = stripSplatFromName pattern.token
       if exp isnt identifier = validIdentifier name
-        addToEnclosingScope name, pattern
+        # TODO: add assumption
+        # addToEnclosingScope name, pattern
         [[identifier, exp]]
       else
         []
@@ -1276,8 +2013,11 @@
           precs: defCache.concat(conds, moreCaches.map(markCache), (recursed.map ({precs}) -> precs)...)
           assigns: [].concat (recursed.map ({assigns}) -> assigns)...

-markCond = (x) -> cond: x
-markCache = (x) -> cache: x
+markCond = (x) ->
+  cond: x
+
+markCache = (x) ->
+  cache: x

 newVar = ->
   "i#{variableCounter++}"
@@ -1364,33 +2104,46 @@
 compileAssign = ([to, from]) ->
   "var #{to} = #{from};"

+# Takes precs and constructs the correct condition
+# if precs empty, returns true
+# preassigns are assignments that are not followed by a condition, so they
+# should be after the condition is checked
 constructCond = (precs) ->
-  return conds: ['true'], preassigns: [] if precs.length is 0
-  i = 0
-  lastCond = false
+  return conds: 'true', preassigns: [] if precs.length is 0
+  lastCond = no
   cases = []
   singleCase = []
-  wrapUp = ->
-    cases.push if singleCase.length is 1
-      singleCase[0]
-    else
-      "(#{singleCase.join ', '})"
+
+  translateCondPart = ({cond, cache}) ->
+    if cond
+      cond
+    else
+      "(#{cache[0]} = #{cache[1]})"
+
+  # Each case is a (possibly empty) list of caching followed by a condition
+  pushCurrentCase = ->
+    condParts = map translateCondPart, singleCase
+    cases.push if condParts.length is 1
+      condParts[0]
+    else
+      "(#{listOf condParts})"
+    singleCase = []
+
   for prec, i in precs
-    if i is 0 and prec.cache
-      continue
-    if prec.cache and lastCond
-      wrapUp()
-      singleCase = []
-    if prec.cache
-      singleCase.push "(#{prec.cache[0]} = #{prec.cache[1]})"
-    else
-      lastCond = yes
-      singleCase.push prec.cond
+    # Don't know if still need to ignore the first cache, probably did
+    # because of the global cache
+    # if i is 0 and prec.cache
+    #   continue
+    if lastCond
+      pushCurrentCase()
+    singleCase.push prec
+    lastCond = prec.cond
+
   preassigns = if lastCond
-    wrapUp()
+    pushCurrentCase()
     []
   else
-    singleCase.map ({cache}) -> cache
+    map _cache, singleCase
   conds: cases.join " && "
   preassigns: preassigns

@@ -1501,22 +2254,19 @@

 # Default type context with builtins

-binaryMathOpType = ['Num', ['Num', 'Num']]
-comparatorOpType = ['a', ['a', 'Bool']]
-
-builtInKinds = ->
-  newMapWith 'Fn', ['*', '*']
-
-builtInContext = ->
-  newMapWith 'true', 'Bool',
-    'false', 'Bool'
-    '&', ['a', ['b', 'b']] # TODO: replace with actual type
+binaryMathOpType = ['Num', 'Num', 'Num']
+comparatorOpType = ['a', 'a', 'Bool']
+
+builtInContext = (ctx) ->
+  concatMaps (mapMap desiplifyTypeAndArity, newMapWith 'True', 'Bool',
+    'False', 'Bool'
+    '&', ['a', 'b', 'b'] # TODO: replace with actual type
     'show-list', ['a', 'b'] # TODO: replace with actual type
     'from-nullable', ['a', 'b'] # TODO: replace with actual type JS -> Maybe a

     # TODO match

-    'if', ['Bool', ['a', ['a', 'a']]]
+    'if', ['Bool', 'a', 'a', 'a']
     # TODO JS interop

     'sqrt', ['Num', 'Num']
@@ -1530,8 +2280,8 @@
     '*', binaryMathOpType
     '=', comparatorOpType
     '!=', comparatorOpType
-    'and', ['Bool', ['Bool', 'Bool']]
-    'or', ['Bool', ['Bool', 'Bool']]
+    'and', ['Bool', 'Bool', 'Bool']
+    'or', ['Bool', 'Bool', 'Bool']

     '-', binaryMathOpType
     '/', binaryMathOpType
@@ -1539,7 +2289,24 @@
     '<', comparatorOpType
     '>', comparatorOpType
     '<=', comparatorOpType
-    '>=', comparatorOpType
+    '>=', comparatorOpType),
+    newMapWith 'empty-array', (type: (new TypeApp arrayType, (ctx.freshTypeVariable star))),
+      'cons-array', (
+        type:
+          (typeFn (elemType = ctx.freshTypeVariable star),
+            (new TypeApp arrayType, elemType),
+            (new TypeApp arrayType, elemType))
+        arity: ['what', 'onto'])
+
+desiplifyTypeAndArity = (simple) ->
+  type: desiplifyType simple
+  arity: ("a#{i}" for _, i in simple[0...simple.length - 1])
+
+desiplifyType = (simple) ->
+  if Array.isArray simple
+    typeFn (map typeConstant, simple)...
+  else
+    typeConstant simple

 # Set/Map implementation
@@ -1588,9 +2355,16 @@
 isSetEmpty = (set) ->
   set.size is 0

+mapSet =
+mapMap = (fn, set) ->
+  initialized = newMap()
+  for key, val of set.values
+    addToMap initialized, key, fn val
+  initialized
+
 newSetWith = (args...) ->
   initialized = newSet()
-  for k in args by 2
+  for k in args
     addToSet initialized, k
   initialized

@@ -1598,6 +2372,12 @@
   initialized = newMap()
   for k, i in args by 2
     addToMap initialized, k, args[i + 1]
+  initialized
+
+newMapKeysVals = (keys, vals) ->
+  initialized = newMap()
+  for item, i in vals
+    addToMap initialized, keys[i], item
   initialized

 concatSets =
@@ -1608,9 +2388,87 @@
       addToMap concated, k, v
   concated

+subtractSets =
+subtractMaps = (from, what) ->
+  subtracted = newMap()
+  for k, v of from.values when k not of what.values
+    addToMap subtracted, k, v
+  subtracted
+
+values = (map) ->
+  map.values
+
 # end of Set

-# Type inference and checker
+# Type inference and checker ala Mark Jones
+
+unify = (ctx, t1, t2) ->
+  sub = ctx.substitution
+  ctx.extendSubstitution findSubToMatch (substitute sub, t1), (substitute sub, t2)
+
+findSubToMatch = (t1, t2) ->
+  if t1 instanceof TypeVariable
+    bindVariable t1, t2
+  else if t2 instanceof TypeVariable
+    bindVariable t2, t1
+  else if t1 instanceof TypeConstr and t2 instanceof TypeConstr and
+    t1.name is t2.name
+      emptySubstitution()
+  else if t1 instanceof TypeApp and t2 instanceof TypeApp
+    s1 = findSubToMatch t1.op, t2.op
+    s2 = findSubToMatch (substitute s1, t1.arg), (substitute s1, t2.arg)
+    joinSubs s1, s2
+  else
+    newMapWith "could not unify", [(printType t1), (printType t2)]
+
+bindVariable = (variable, type) ->
+  if type instanceof TypeVariable and variable.name is type.name
+    emptySubstitution()
+  else if inSet (findFree type), variable.name
+    newSetWith variable.name, "occurs check failed"
+  else if not kindsEq (kind variable), (kind type)
+    newSetWith variable.name, "kinds don't match for #{variable.name}"
+  else
+    newMapWith variable.name, type
+
+joinSubs = (s1,s2) ->
+  concatSets s1, mapMap ((type) -> substitute s1, type), s2
+
+emptySubstitution = ->
+  newMap()
+
+substitute = (substitution, type) ->
+  if type instanceof TypeVariable
+    if sub = lookupInMap substitution, type.name
+      sub
+    else
+      type
+  else if type instanceof TypeApp
+    new TypeApp (substitute substitution, type.op),
+      (substitute substitution, type.arg)
+  else if isBinding type
+    newBinding mapMap ((t) -> substitute substitution t) type
+  else
+    type
+
+findFree = (type) ->
+  if type instanceof TypeVariable
+    newMapWith type.name, type.kind
+  else if type instanceof TypeApp
+    concatMaps (findFree type.op), (findFree type.arg)
+  else
+    newMap()
+
+findBound = (name, binding) ->
+  (lookupInMap binding, name) or 'unbound name #{name}'
+
+# TODO: when using qualified types, replace with
+# freshInst over type schemes
+freshInstance = (ctx, type) ->
+  freshes = mapMap ((kind) -> ctx.freshTypeVariable kind), findFree type
+  substitute freshes, type
+
+# Old type inference

 assignTypes = (context, expression) ->
   [s, _, expression] = infer context, expression, 0
@@ -1666,14 +2524,13 @@
 tokens = (words) ->
   words.map (x) -> x.token

-
 infer = (context, expression, nameIndex) ->
   switch expression.label
     when 'numerical'
       expression.tea = 'Num'
       [newMap(), nameIndex, expression]
     when 'string'
-      expression.tea = if /$"/.test expression.token then 'String' else 'Char'
+      expression.tea = if /^"/.test expression.token then 'String' else 'Char'
       [newMap(), nameIndex, expression]
     else
       # Reference
@@ -1724,11 +2581,6 @@
   s3 = unify (subExp s2, opTea), [arg.tea, returnName]
   [(concatMaps s3, s2, s1), nextIndex, (subExp s3, returnName)]

-
-
-unify = (t1, t2) ->
-  unifyWith newMap(), [[t1, t2]]
-
 unifyWith = (subs, pairs) ->
   if pairs.length is 0
     subs
@@ -1799,36 +2651,112 @@
   suffix = if nameIndex > 25 then Math.floor nameIndex / 25 else ''
   String.fromCharCode 97 + nameIndex % 25

-typeVariable = (name) ->
-  new TypeVariable name
-
-nullaryType = (name) ->
-  new NullaryType name
-
-naryType = (name, params) ->
-  new PolyType name, params
-
-class MonoType
-  constructor: (@name) ->
-
-class TypeVariable extends MonoType
-class NullaryType extends MonoType
-
-class PolyType
-  constructor: (@name, @params) ->
-
-class NaryType extends PolyType
-
-ignoreTypeErrors = (fn) ->
-  try
-    ret = fn()
-  catch e
-    unless e instanceof TypeError
-      throw e
-
-class TypeError extends Error
-  # required for instanceof to work
-  constructor: (@message, @data) -> super @message
+# Kind (data
+#   Star
+#   KindFn [from: Kind to: Kind])
+#
+# Type (data
+#   TypeVariable [var: TypeVariable]
+#   KnownType [const: TypeConstr]
+#   TypeApplication [op: Type arg: Type]
+#   QuantifiedVar [var: Int])
+#
+# TypeVariable (record name: String kind: Kind)
+# TypeConstr (record name: String kind: Kind)
+
+isBinding = (x) ->
+  if not x
+    throw new Error "nothing passed to isBinding"
+  x.binding
+
+newBinding = (map) ->
+  map.binding = yes
+  map
+
+typeConstant = (name) ->
+  new TypeConstr name, star
+
+tupleType = (arity) ->
+  new TypeConstr "[#{arity}]", kindFn arity
+
+kindFn = (arity) ->
+  if arity is 1
+    new KindFn star, star
+  else
+    new KindFn star, kindFn arity - 1
+
+typeFn = (from, to, args...) ->
+  if args.length is 0
+    new TypeApp (new TypeApp arrowType, from), to
+  else
+    typeFn from, (typeFn to, args...)
+
+applyKindFn = (fn, arg, args...) ->
+  if args.length is 0
+    new TypeApp fn, arg
+  else
+    applyKindFn (applyKindFn fn, arg), args...
+
+isConstructor = (type) ->
+  (kind type) instanceof KindFn
+
+kind = (type) ->
+  if type.kind
+    type.kind
+  else if type instanceof TypeApp
+    (kind type.op).to
+  else
+    throw "Invalid type in kind"
+
+kindsEq = (k1, k2) ->
+  k1 is k2 or
+    (kindsEq k1.from, k2.from) and
+    (kindsEq k1.to, k2.to)
+
+class KindFn
+  constructor: (@from, @to) ->
+
+class TypeVariable
+  constructor: (@name, @kind) ->
+class TypeConstr
+  constructor: (@name, @kind) ->
+class TypeApp
+  constructor: (@op, @arg) ->
+class QuantifiedVar
+  constructor: (@var) ->
+
+star = '*'
+arrowType = new TypeConstr '->', kindFn 2
+arrayType = new TypeConstr 'Array', kindFn 1
+
+kind = (type) ->
+  if type.kind
+    kind
+  else if type.op
+    kind type.op
+  else
+    throw "Invalid type in kind"
+
+printType = (type) ->
+  if type instanceof TypeVariable
+    type.name
+  else if type instanceof TypeConstr
+    type.name
+  else if type instanceof TypeApp
+    "(#{printType type.op} #{printType type.arg})"
+  else
+    type
+
+# ignoreTypeErrors = (fn) ->
+#   try
+#     ret = fn()
+#   catch e
+#     unless e instanceof TypeError
+#       throw e
+
+# class TypeError extends Error
+#   # required for instanceof to work
+#   constructor: (@message, @data) -> super @message

 # mycroft = (context, node, environment) ->
 #   if node.label is 'numerical'
@@ -1861,10 +2789,6 @@
 # defaultEnvironment = (name) ->
 #   switch name
 #     when '+' then ['Int', 'Int']
-
-
-
-

 library = """
 var $listize = function (list) {
@@ -1963,16 +2887,9 @@
   return t;
 };

-var $curry = function (f) {
-  var _curry = function(args) {
-    return f.length == 1 ? f : function(){
-      var params = args ? args.concat() : [];
-      return params.push.apply(params, arguments) < f.length && arguments.length
-        ? _curry.call(this, params)
-        : f.apply(this, params);
-    };
-  };
-  return _curry();
+var λ = function (n, f) {
+  f._ = n;
+  return f;
 };

 var from__nullable = function (jsValue) {
@@ -1982,7 +2899,23 @@
     return {':just': [jsValue]};
   }
 };
-
+""" +
+(for i in [1..9]
+  varNames = "abcdefghi".split ''
+  first = (j) -> varNames[0...j].join ', '
+  # TODO: handle A9 first branch
+  """var _#{i} = function (f, #{first i}) {
+    if (f._ === #{i} || f.length === #{i}) {
+      return f(#{first i});
+    } else if (f._ > #{i} || f.length > #{i}) {
+      return function (#{varNames[i]}) {
+        return _#{i + 1}(f, #{first i + 1});
+      };
+    } else {
+      return _1(#{if i is 1 then "f()" else "_#{i - 1}(f, #{first i - 1})"}, #{varNames[i - 1]});
+    }
+  };""").join('\n\n') +
+"""
 ;
 """


Fix order of modules searched in findMatchingDefs

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -5112,7 +5112,7 @@
     }
   else
     module = lookupCompiledModule moduleName
-    {modules, ctx} = contextWithDependencies moduleName
+    {modules, ctx} = contextWithDependencies moduleDependencies moduleName
     [expression] = _terms ast
     compilationFn = (topLevelExpressionInModule importsFor moduleDependencies moduleName)
     {js} = compileCtxAstToJs compilationFn, ctx, expression
@@ -5126,13 +5126,15 @@
     setToArray (lookupCompiledModule name).declared.definitions
   mapKeys lookupDefinitions, moduleSet

-contextWithDependencies = (moduleName) ->
-  toInject = moduleDependencies moduleName
-  ctx: injectedContext toInject
-  modules: setToArray toInject
+contextWithDependencies = (modules) ->
+  ctx: injectedContext modules
+  modules: setToArray modules

 moduleDependencies = (moduleName) ->
   concatSets (collectRequiresFor moduleName), (newSetWith moduleName)
+
+reverseModuleDependencies = (moduleName) ->
+  concatSets (newSetWith moduleName), (collectRequiresFor moduleName)

 # Primitive type checking for now
 checkTypes = (ctx) ->
@@ -5203,7 +5205,7 @@

 findMatchingDefinitions = (moduleName, reference) ->
   {declared: {savedScopes}} = lookupCompiledModule moduleName
-  {ctx} = contextWithDependencies moduleName
+  {ctx} = contextWithDependencies reverseModuleDependencies moduleName
   {scope, type} = reference
   return [] unless scope?
   scoped =
@@ -5223,6 +5225,7 @@

 findMatchingDefinitionsOnType = (type, definitionLists) ->
   ctx = new Context
+  console.log definitionLists
   [typed, untyped] = unzip (for definitions, i in definitionLists
     isValid = (name, def) ->
       def.type? and not def.type.TempType
@@ -5236,7 +5239,7 @@
         score = -Infinity
       else
         score = -((subMagnitude sub) + i * 100)
-      type: plainPrettyPrint def.type#score + ' ' +
+      type: score + ' ' + plainPrettyPrint def.type#score + ' ' +
       arity: def.arity
       docs: def.docs
       rawType: def.type


Use indentation to direct toplevel compilation

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1396,10 +1396,7 @@
     (irDefinition expression.tea, compiled, null, yes)

 topLevel = (ctx, form) ->
-  if (terms = _validTerms form).length % 2 == 0
-    definitionList ctx, pairs terms
-  else
-    throw new Error "Missing definition at top level"
+  definitionList ctx, spaceSeparatedPairs form

 topLevelModule = (moduleName, defaultImports) -> (ctx, form) ->
   [(jsAssignStatement (jsAccess "Shem", (validIdentifier moduleName)),
@@ -3407,6 +3404,29 @@

 _labeled = (list) ->
   pairsLeft isLabel, list
+
+spaceSeparatedPairs = (nodes) ->
+  isNewLine = (token) ->
+    token.symbol is '\n'
+  pairs = []
+  for node in nodes
+    newLine = isNewLine node
+    expression = isExpressionOrFake node
+    if not lhs and expression and not newLine
+      lhs = node
+      space = no
+    else if lhs and (expression and not newLine or
+        space and newLine)
+      pairs.push [lhs, node]
+      lhs = no
+    else if lhs and newLine
+      pairs.push [lhs, fake_()]
+      lhs = no
+    else if not expression
+      space = yes
+  if lhs
+    pairs.push [lhs, fake_()]
+  pairs

 pairsLeft = (leftTest, list) ->
   listToPairsWith list, (item, next) ->


Single fn works but cycles on more than one

--- src/js/app/compilers/teascript/compiler.coffee
+++ src/js/app/compilers/teascript/compiler.coffee
@@ -1170,6 +1170,7 @@
   return if set.values[key]
   set.size += 1
   set.values[key] = value
+  set

 removeFromSet =
 removeFromMap = (set, key) ->
@@ -1198,14 +1199,12 @@
   clone = newSet()
   addAllToSet clone, setToArray set

+lookupInMap =
 inSet = (set, name) ->
   set.values[name]

 isSetEmpty = (set) ->
   set.size is 0
-
-lookupInMap = (set, key) ->
-  set.values[key]

 unionMaps = (first, second) ->
   union = (cloneSet first)
@@ -1226,13 +1225,13 @@

 infer = (context, expression, nameIndex) ->
   switch expression.label
-    when 'numerical' then [[], 'Num']
-    when 'string' then [[], 'Text']
+    when 'numerical' then [newMap(), 'Num']
+    when 'string' then [newMap(), 'Text']
     else
       # Reference
       if isReference expression
         # TODO: replace free type variables with new unused names
-        [[], lookupInMap context, expression.token]
+        [newMap(), lookupInMap context, expression.token]
       # Lambda
       else if expression.type is 'function'
         {params, body, wheres} = fnDefinition expression
@@ -1252,25 +1251,29 @@
         returnName = freshName nameIndex
         [s1, A] = infer context, op
         [s2, B] = infer (subContext s1, context), arg
+        log "op", (subExp s2, A)
+        log "operand", [B, returnName]
         s3 = unify (subExp s2, A), [B, returnName]
-        [(concat s3, s2, s1), (subExp returnName)]
+        log "s3", s3
+        [(concat s3, s2, s1), (subExp s3, returnName)]

 unify = (t1, t2) ->
-  unifyWith [], [t1, t2]
+  unifyWith newMap(), [t1, t2]

 unifyWith = (subs, pairs) ->
   if pairs.length is 0
     subs
   else
-    [[t1, t2], rest] = pairs
+    [[t1, t2], rest...] = pairs
     if t1 is t2
       unifyWith subs, rest
     else if isTypeVariable t1
+      log "left", t1
       sub = addToMap newMap(), t1, t2
-      unifyWith (addToMap subs, t1, t2), subPairs rest
+      unifyWith (addToMap subs, t1, t2), subPairs sub, rest
     else if isTypeVariable t2
       sub = addToMap newMap(), t2, t1
-      unifyWith (addToMap subs, t2, t1), subPairs rest
+      unifyWith (addToMap subs, t2, t1), subPairs sub, rest
     else if not (isTypeVariable t1) and not (isTypeVariable t2)
       [t1from, t1to] = t1
       [t2from, t2to] = t2
@@ -1299,14 +1302,18 @@
   newContext

 isTypeVariable = (name) ->
-  name.charCodeAt(0) < 97 + 26
+  name.charCodeAt(0) >= 97

 freshName = (nameIndex) ->
   # TODO: handle more than 27 variables
   String.fromCharCode 97 + nameIndex

-concat = (arrays...) ->
-  [].concat arrays...
+concat = (maps...) ->
+  concated = newMap()
+  for map in maps
+    for k, v of map.values
+      addToMap concated, k, v
+  concated

 # mycroft = (context, node, environment) ->
 #   if node.label is 'numerical'


Make despawn and rmeove safer

Make despawn and rmeove safer

--- src/EntityManager.coffee
+++ src/EntityManager.coffee
@@ -63,6 +63,7 @@
     )

   removeEntity:(entity)->
+    return if !entity
     console.log("removing entity: #{entity.type} #{entity.name}")
     @entities[entity.id].remove()
     delete @entities[entity.id]
@@ -135,6 +136,8 @@
     entity.owner = newOwner

   despawnEntity:(entity)->
+    return if !entity
+
     console.log("despawning entity: #{entity.type} #{entity.name}")
     @broadcastDespawnEntity(entity)
     @removeEntity(entity)


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/sprite.coffee
+++ src/sprite.coffee
@@ -107,7 +107,8 @@
   ###
   Sprite.load = (url, loadedCallback) ->
     if spriteCache[url]
-    spriteCache[url]
+      spriteCache[url]
+
     img = new Image()
     proxy = LoaderProxy()


cleanup roles interaction a bit

cleanup roles interaction a bit

--- src/hubot/scripts/roles.coffee
+++ src/hubot/scripts/roles.coffee
@@ -11,12 +11,16 @@
   robot.respond /who is ([\w .-]+)\?*$/i, (msg) ->
     name = msg.match[1]

-    if user = robot.userForName name
+    if name == "you"
+      msg.send "Who ain't I?"
+    else if name == robot.name
+      msg.send "The best."
+    else if user = robot.userForName name
       user.roles = user.roles or [ ]
       if user.roles.length > 0
         msg.send "#{name} is #{user.roles.join(", ")}."
       else
-        msg.send "#{name} doesn't have any roles yet"
+        msg.send "#{name} is nothing to me."
     else
       msg.send "#{name}? Never heard of 'em"

@@ -33,10 +37,12 @@
             msg.send "I know"
           else
             user.roles.push(newRole)
-            msg.send "Ok, #{name} is #{newRole}"
-
+            if name.toLowerCase() == "hubot"
+              msg.send "Ok, I am #{newRole}."
+            else
+              msg.send "Ok, #{name} is #{newRole}."
         else
-          msg.send "#{name}? Never heard of 'em"
+          msg.send "I don't know anything about #{name}."

   robot.respond /([\w .-]+) is not (["'\w: ]+)[.!]*$/i, (msg) ->
     name    = msg.match[1]
@@ -47,10 +53,10 @@
         user.roles = user.roles or [ ]

         if newRole not in user.roles
-          msg.send "I know"
+          msg.send "I know."
         else
           user.roles = (role for role in user.roles when role != newRole)
           msg.send "Ok, #{name} is no longer #{newRole}."

       else
-        msg.send "#{name}? Never heard of 'em"
+        msg.send "I don't know anything about #{name}."


lingering fs > path update for updated node API

--- src/stitch.coffee
+++ src/stitch.coffee
@@ -14,7 +14,7 @@
   # Private

   walk: (path, parent = path, result = []) ->
-    return unless npath.existsSync(path)
+    return unless fs.existsSync(path)
     for child in fs.readdirSync(path)
       child = npath.join(path, child)
       stat  = fs.statSync(child)
@@ -36,4 +36,4 @@
   valid: ->
     !!compilers[@ext]

-module.exports = Stitch+module.exports = Stitch


* use the subkey-iterator

* use the subkey-iterator

--- src/subkey.coffee
+++ src/subkey.coffee
@@ -368,7 +368,7 @@

       #the opts.path could be relative
       opts.path = getPathArray(opts.path, @_pathArray) or @_pathArray
-      stream = aCreateReadStream(aDbCore, opts, aDbCore.createDecoder(opts))
+      stream = aCreateReadStream(aDbCore, opts)
       stream
     createReadStream: @::readStream


Add sourcing of .zshrc to atom-ruby-test config

Necessary to give it access to an initialized rbenv

--- atom/config.cson
+++ atom/config.cson
@@ -34,8 +34,8 @@
     wordDiff: false
   "merge-conflicts": {}
   "ruby-test":
-    rspecFileCommand: "spring rspec --tty {relative_path}"
-    rspecSingleCommand: "spring rspec --tty {relative_path}:{line_number}"
+    rspecFileCommand: "source ~/.zshrc && spring rspec --tty {relative_path}"
+    rspecSingleCommand: "source ~/.zshrc && spring rspec --tty {relative_path}:{line_number}"
     shell: "zsh"
     specFramework: "rspec"
   welcome:


Switch from fast Fuzzy Finder scoring system to standard

e.g. if I type "headertpl", the fast system will match header_logo.tpl,
while the standard system will correctly match header.tpl.

--- atom/config.cson
+++ atom/config.cson
@@ -124,7 +124,7 @@
       "LocalDistribution"
     ]
     preserveLastSearch: true
-    scoringSystem: "fast"
+    scoringSystem: "standard"
     useRipGrep: true
   "git-blame":
     colorCommitAuthors: true


Remove pigments from deactivated package list

It was removed from the packages.cson file in ccee403.

--- atom/config.cson
+++ atom/config.cson
@@ -27,7 +27,6 @@
       "language-perl"
       "metrics"
       "open-on-github"
-      "pigments"
       "release-notes"
       "script"
       "timecop"


Update config.cson for Atom

Update config.cson for Atom

--- atom/config.cson
+++ atom/config.cson
@@ -1,32 +1,46 @@
 "*":
+  "activate-power-mode":
+    autoToggle: false
   "autocomplete-emojis":
     enableUnicodeEmojis: false
   "autocomplete-plus":
     enableAutoActivation: false
     minimumWordLength: 1
   core:
-    customFileTypes: {
-      "source.ruby": ["god"]
-    }
+    customFileTypes:
+      "source.ruby": [
+        "god"
+      ]
     destroyEmptyPanes: false
     disabledPackages: [
       "nvatom"
+      "branch-status"
+      "linter"
+      "linter-rubocop"
+      "indent-guide-improved"
     ]
     ignoredNames: [
       ".DS_Store"
       ".git"
       "vendor"
     ]
+    telemetryConsent: "no"
+    useCustomTitleBar: true
   editor:
     autoIndentOnPaste: false
     fontSize: 11
     invisibles: {}
     scrollPastEnd: true
+    showIndentGuide: true
+  "file-icons":
+    onChanges: true
   "find-and-replace":
     openProjectFindResultsInRightPane: true
   "git-projects":
     maxDepth: 1
     rootPath: "~/github;~/src"
+  "highlight-line":
+    enableBackgroundColor: false
   "markdown-writer": {}
   "merge-conflicts":
     gitPath: "bin/git"
@@ -46,6 +60,12 @@
       "source.gfm"
       "text.git-commit"
     ]
+  "split-diff":
+    diffWords: true
+    ignoreWhitespace: true
+    leftEditorColor: "red"
+    rightEditorColor: "green"
+    syncHorizontalScroll: true
   tabs:
     enableVcsColoring: true
   welcome:


Fix deprecation warning in init.coffee

Fix deprecation warning in init.coffee

Warning text:

> This method is deprecated on the model layer. Use
> TextEditorElement::pixelPositionForScreenPosition instead

--- atom/init.coffee
+++ atom/init.coffee
@@ -132,7 +132,7 @@
     cursorPosition = textEditor.getCursorScreenPosition()
     scrollBottomPosition = [cursorPosition.row + 1, cursorPosition.column]
     pixelPositionForScrollBottomPosition =
-      textEditor.pixelPositionForScreenPosition(scrollBottomPosition)
+      textEditorElement.pixelPositionForScreenPosition(scrollBottomPosition)
     textEditorElement.setScrollBottom(pixelPositionForScrollBottomPosition.top)

   # Move the cursor to the next diff in the editor, and then scroll the view


Atom - 'space p' show Atom menu commands, pallete

--- atom/keymap.cson
+++ atom/keymap.cson
@@ -46,3 +46,6 @@
     # Focus panes - 'space 2' - next, 'space 1' - previous
     'space 2': 'window:focus-next-pane'
     'space 1': 'window:focus-previous-pane'
+
+    # Show Atom preferences pallete settings/commands
+    'space p': 'command-palette:toggle'


:memo: Doc params in window.coffee

:memo: Doc params in window.coffee

--- src/window.coffee
+++ src/window.coffee
@@ -1,9 +1,8 @@
 # Public: Measure how long a function takes to run.
 #
-# * description:
-#   A {String} description that will be logged to the console.
-# * fn:
-#   A {Function} to measure the duration of.
+# description - A {String} description that will be logged to the console when
+#               the function completes.
+# fn - A {Function} to measure the duration of.
 #
 # Returns the value returned by the given function.
 window.measure = (description, fn) ->
@@ -15,13 +14,11 @@

 # Public: Create a dev tools profile for a function.
 #
-# * description:
-#   A {String} descrption that will be available in the Profiles tab of the dev
-#   tools.
-# * fn:
-#   A {Function} to profile.
+# description - A {String} description that will be available in the Profiles
+#               tab of the dev tools.
+# fn - A {Function} to profile.
 #
-# Return the value returned by the given function.
+# Returns the value returned by the given function.
 window.profile = (description, fn) ->
   measure description, ->
     console.profile(description)


Function#delay and Function#defer

Function#delay and Function#defer

--- src/function_extensions.coffee
+++ src/function_extensions.coffee
@@ -51,3 +51,13 @@
     func.apply(this, arguments)
     return x

+Function::delay = (wait, args...) ->
+  func = this
+
+  setTimeout ->
+    func.apply(null, args)
+  , wait
+
+Function::defer = (args...) ->
+  this.delay.apply this, [1].concat(args)
+


修复bug

修复bug

--- biz/issue.coffee
+++ biz/issue.coffee
@@ -62,7 +62,7 @@
   find: (data, cb)->
     self = @
     cond = {}
-    cond.id = data.id
+    cond['issue.id'] = data.id
     cond.project_id = data.project_id

     #选项


tweaks

tweaks

--- src/javascript/admin_ui.coffee
+++ src/javascript/admin_ui.coffee
@@ -1,6 +1,5 @@
 React = require 'react'
-# Slider = require('react-rangeslider')
-# ReactSlider = require('react-slider')
+classnames = require 'classnames'

 Immutable = require 'immutable'
 RollingHistory = require './utils/state_history2'
@@ -9,11 +8,8 @@
 {div,span,table,tbody,td,tr} = React.DOM

 createToggleLi = (address,text,action,state) ->
-  attrs = {}
-  attrs.className = "enabled" if state
-  attrs.onClick = ->
-    address.send Map(type: 'AdminUIEvent', name: action)
-  React.createElement('li', attrs, text)
+  sendAction = -> address.send Map(type: 'AdminUIEvent', name: action)
+  React.createElement('li', {onClick: sendAction, className:classnames(enabled:state)}, text)

 AdminUI = React.createClass
   displayName: 'AdminUI'


Make route a bit more fluent

Make route a bit more fluent

--- src/lazerscripts/stage1.coffee
+++ src/lazerscripts/stage1.coffee
@@ -290,9 +290,7 @@
     repeatPattern = (height) ->
       [
         [.2, height]
-        [.1, height + .1]
         [.2, height + .2]
-        [.4, height + .1]
         [.6, height]
         [1.1, height]
       ]


Fixed appManager.openApplication.

Fixed appManager.openApplication.

--- client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
+++ client/landing/app/MainApp/filetree/controllers/findertreecontroller.coffee
@@ -501,7 +501,7 @@
               @getSingleton('mainController').emit 'CreateNewActivityRequested', 'JCodeShare', CodeShares

   openTerminalFromHere: (nodeView) ->
-    appManager.openApplication "WebTerm", yes, (appInstance) =>
+    appManager.open "WebTerm", yes, (appInstance) =>
       path        = nodeView.getData().path
       webTermView = KD.getSingleton('mainView').mainTabView.getActivePane().mainView
       #TODO: webTermView != appInstance.getView() so should simplify the line above


clear console.log

clear console.log

--- src/BuscaCep.coffee
+++ src/BuscaCep.coffee
@@ -21,7 +21,6 @@

     populate: (data)->
       data = @returnConverter(data)
-      console.log data
       @setVal '[data-endereco-cidade-id]', data.city.id
       @setVal '[data-endereco-cidade]', data.city.nome+'-'+data.city.uf
       @setVal '[data-endereco-logradouro]', data.street.nome


On browser side, normalize component paths to .js

--- src/lib/ComponentLoader.coffee
+++ src/lib/ComponentLoader.coffee
@@ -34,6 +34,8 @@
       moduleName = moduleName.substr 1
     if definition.noflo.components
       for name, cPath of definition.noflo.components
+        if cPath.indexOf('.coffee') isnt -1
+          cPath = cPath.replace '.coffee', '.js'
         @registerComponent prefix, name, "/#{moduleName}/#{cPath}"
     if definition.noflo.graphs
       for name, cPath of definition.noflo.graphs


Wait for open to finish before making assertion

Previously, this was just relying on a 0ms wait. After this change we
actually wait until the open call completes.

--- spec/deprecation-cop-status-bar-view-spec.coffee
+++ spec/deprecation-cop-status-bar-view-spec.coffee
@@ -64,9 +64,15 @@

   it 'opens deprecation cop tab when clicked', ->
     expect(atom.workspace.getActivePane().getActiveItem()).not.toExist()
+
+    # Store the last result of calling open so we can make assertions afterwards.
+    lastOpenResult = null
+    {open} = atom.workspace
+    spyOn(atom.workspace, 'open').andCallFake (args...) ->
+      lastOpenResult = open.apply atom.workspace, args
+
     statusBarView.click()
-
-    waits 0
+    waitsForPromise -> lastOpenResult
     runs ->
       depCopView = atom.workspace.getActivePane().getActiveItem()
       expect(depCopView instanceof DeprecationCopView).toBe true


fix new proposal permissions problem for unregistered users

Former-commit-id: b624f6f33cd518109a3f5a1ef531db85fce03160 [formerly 1bfa633f6c0a4c24da41b156c4eda3d1087c97e7] [formerly 59232c555c22956df73051e32bfe2419f6a5600b [formerly e416f4bc9445f16a2a7d5b505bc64347a81799cb]] [formerly 3481ae71f89a480574b5b8438572ef563df33656 [formerly ad86e7d5aca1f7ec359c6f404ea54f4d7355c078] [formerly e93f6037c536e9cf0b47a5207259c84ab8251360 [formerly e93f6037c536e9cf0b47a5207259c84ab8251360 [formerly 866922bdad067f72ac56a095c810cbad1777b4a4]]]]
Former-commit-id: 3783a2abf7894b97866ba5e99d76f9548b2bc273 [formerly 7aa095dd6cbe6e2f0c0eff7a4d9d87ceef7cc7f0] [formerly b00b3d75c698e880087b2ba364d999d8300b5118 [formerly 7a6e064d4b51250d482b22c316651b42b4e1141d]]
Former-commit-id: 499e60cd7bc6320c0f8785ed0ed340d67813ef44 [formerly 13d8e6372c6836f08e486617a23dd02965d2c72c]
Former-commit-id: 248e1de2411792026b4f037411667fca86577786
Former-commit-id: 0979ad39eb15fb78bc9a1febb0a6c76db2336578

--- @client/permissions.coffee
+++ @client/permissions.coffee
@@ -54,7 +54,7 @@

     when 'update proposal', 'delete proposal'
       proposal = fetch arguments[1]
-      if !current_user.is_admin && !matchEmail(proposal.roles.editor)
+      if !current_user.is_admin && (proposal.key == 'new_proposal' || !matchEmail(proposal.roles.editor) )
         return Permission.INSUFFICIENT_PRIVILEGES

     when 'publish opinion'


Setup extra test helpers

Setup extra test helpers

--- project_template/spec/spec_helper.coffee
+++ project_template/spec/spec_helper.coffee
@@ -1,3 +1,29 @@
 Backbone = require('backbone')
 # Backbone.$ is not automatically set when backbone is loaded as CommonJS module
 $ = Backbone.$ = require('jquery')
+
+# Allow to use memo-isation in tests
+#
+# describe 'some feature', ->
+#
+#   data = memo().is -> { foo: 'bar' }
+#
+#   it 'shows bar', ->
+#     expect(data().foo).to.eql 'bar'
+#
+#   describe 'when undefined', ->
+#     data.is -> { foo: undefined }
+#
+#     it 'shows undefined', ->
+#       expect(data().foo).to.be.undefined
+#
+window.memo = require('memo-is')
+
+When = require('when')
+window.wait = (delay = 1) ->
+  d = When.defer()
+  setTimeout(
+    -> d.resolve()
+    delay
+  )
+  d.promise


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/color.coffee
+++ test/color.coffee
@@ -35,6 +35,11 @@
   equals color.g(), 255
   equals color.b(), 255

+  color = Color('#3ef')
+  equals color.r(), 44
+  equals color.g(), 39
+  equals color.b(), 11
+
 test "Color should know what Fuzzy Wuzzy Brown is", ->
   color = Color("Fuzzy Wuzzy Brown")
   equals color.r(), 196


[test] RedisModel##fullKeysForList

[test] RedisModel##fullKeysForList

--- test/index.coffee
+++ test/index.coffee
@@ -231,3 +231,28 @@
             expect(lists.length).to.equal 1

           done()
+
+  describe "##fullKeysForList", ->
+    it "should find queues for unknown ids", (done) ->
+      ideally = errify done
+      data    = {name: "testjob"}
+      await fakeJob queuename, data, null, ideally defer {queue, job}
+      list = ids: "*": [job.jobId]
+
+      await instance.fullKeysForList list, ideally defer fullKeys
+      [fullKey] = fullKeys
+      expect(fullKey).to.equal "bull:#{queuename}:#{job.jobId}"
+
+      qCleaner(queue).asCallback done
+
+    it "should return the full keynames for ids", (done) ->
+      ideally = errify done
+      list    = ids: {}
+      id      = 7
+      list.ids[queuename] = [id]
+
+      await instance.fullKeysForList list, ideally defer fullKeys
+      [fullKey] = fullKeys
+      expect(fullKey).to.equal "bull:#{queuename}:#{id}"
+
+      done()


Add launch link

Add launch link

--- src/add/html.coffee
+++ src/add/html.coffee
@@ -40,7 +40,7 @@
         hr()
         text 'Пользователей требуется создать в самом Directum:'
         ol ->
-          li 'Откройте оснастку "Пользователи"'
+          li 'Откройте оснастку "', (-> a href: '#', 'Пользователи'), '"'
           li 'Добавьте новую запись (Ctrl+N)'
           li 'Укажите имя (учётную запись в домене)'
           li 'Введите какое-нибудь полное имя (можно ту же учётную запись)'


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/state.coffee
+++ test/state.coffee
@@ -1,12 +1,14 @@
 test "State events should transition properly", ->
-  animation = Core
+  animation = GameObject
     initialState: 'stand'
-
-  animation.include(Bindable)
+
   animation.include(StateMachine)

   animation.transition('run')

   equals animation.currentState(), "run"

+test "", ->
+  animation = Core
+


Changed exitFullscreen method, now it returns a promise, which is resolved after exit fullscreen. See #45965

--- src/base/dom.coffee
+++ src/base/dom.coffee
@@ -1476,6 +1476,7 @@
 		dfr.promise()

 	@exitFullscreen: ->
+		dfr = new CUI.Deferred()

 		if document.exitFullscreen
 			document.exitFullscreen()
@@ -1485,7 +1486,19 @@
 			document.mozCancelFullScreen()
 		else if (document.webkitExitFullscreen)
 			document.webkitExitFullscreen()
-		return
+
+		fullscreenEvent = CUI.Events.listen
+			type: "fullscreenchange"
+			node: window
+			call: () =>
+				if not CUI.dom.isFullscreen()
+					dfr.notify()
+				else
+					CUI.Events.ignore(fullscreenEvent)
+					dfr.resolve()
+				return
+
+		return dfr.promise()

 	@fullscreenElement: ->
 		document.fullscreenElement or


namespaced unbind

namespaced unbind

--- src/bindable.coffee
+++ src/bindable.coffee
@@ -46,7 +46,7 @@
   bind: (namespacedEvent, callback) ->
     [event, namespace] = namespacedEvent.split(".")

-    # HACK: Here we annotate the callback
+    # HACK: Here we annotate the callback function with namespace metadata
     if namespace
       callback.__PIXIE ||= {}
       callback.__PIXIE[namespace] = true


tests

tests

--- test/tests.coffee
+++ test/tests.coffee
@@ -10,6 +10,7 @@
   csvDelimiter:     ','
   csvQuote:         '"'
   csvEscape:        '"'
+  prettyCellSpace:  ' '
 }

 csvOptions = {
@@ -93,3 +94,26 @@
       /_a……………………………_$_b…………_$_c_1………………………………………_$_c_2……_\\
     """

+  it 'expect to transform also empty cells', (done) ->
+    csvData = """
+      foo,bar,baz
+      a#,"b",c
+      d,e,
+      ,f,
+      ,,
+      g,h,i
+    """
+    parse csvData.trim(), {}, (err, rows) ->
+      expect(err).to.be.equal null
+      csv2md.setOptions(defaultOptions)
+      csv2md.setOptions(pretty: true)
+      expect(csv2md.rowsToString(rows).trim()).to.be.equal """
+        | foo | bar | baz |
+        |-----|-----|-----|
+        | a#  | b   | c   |
+        | d   | e   |     |
+        |     | f   |     |
+        |     |     |     |
+        | g   | h   | i   |
+      """.trim()
+      done()


Match test code style to docco (camelCase naming)

--- test/tests.coffee
+++ test/tests.coffee
@@ -4,9 +4,9 @@
 fs            = require 'fs'

 # Determine the test and resources paths
-test_path      = path.dirname fs.realpathSync(__filename)
-data_path      = path.join test_path, "data"
-resources_path = path.normalize path.join(test_path,"/../resources")
+testPath      = path.dirname fs.realpathSync(__filename)
+dataPath      = path.join testPath, "data"
+resourcesPath = path.normalize path.join(testPath,"/../resources")

 #### Docco Test Assertion Wrapper

@@ -14,19 +14,19 @@
 # is equal to what is expected.  We assume there is one CSS file
 # that is always copied to the output, so we check that the
 # number of output files is (matched_sources + 1).
-test_docco_run = (test_name,sources,options=null,callback=null) ->
-  dest_path = path.join data_path, test_name
+testDoccoRun = (test_name,sources,options=null,callback=null) ->
+  destPath = path.join dataPath, test_name
   # Remove the data directory for this test run
-  cleanup = (callback) -> exec "rm -rf #{dest_path}", callback
+  cleanup = (callback) -> exec "rm -rf #{destPath}", callback
   cleanup ->
-    options?.output = dest_path
+    options?.output = destPath
     Docco.document sources, options, ->
       # Calculate the number of expected files in the output, and
       # then the number of files actually found in the output path.
       files       = []
       files       = files.concat(Docco.resolveSource(src)) for src in sources
       expected    = files.length + 1
-      found       = fs.readdirSync(dest_path).length
+      found       = fs.readdirSync(destPath).length

       # Check the expected number of files against the number of
       # files that were actually found.
@@ -41,16 +41,16 @@
 # expected output.  This exercises the no options branches of
 # logic.
 test "documenting Docco", ->
-  docs_path = path.normalize("#{test_path}/../docs")
-  sources   = ["#{test_path}/../src/docco.coffee","#{test_path}/tests.coffee"]
+  docsPath = path.normalize("#{testPath}/../docs")
+  sources   = ["#{testPath}/../src/docco.coffee","#{testPath}/tests.coffee"]

   # We need to remove the docs/ directory to ensure it contains
   # no files.  If it contains files when this test is run, the
   # assertion checks could be compromised, making the test itself
   # quite britle.
-  exec "rm -rf #{docs_path}", ->
+  exec "rm -rf #{docsPath}", ->
     Docco.document sources,null, ->
-      found       = fs.readdirSync(docs_path).length
+      found       = fs.readdirSync(docsPath).length
       files       = []
       files       = files.concat(Docco.resolveSource(src)) for src in sources
       expected    = files.length + 1
@@ -60,55 +60,55 @@

 # Verify we can use a custom jst template file
 test "custom JST template file", ->
-  test_docco_run "custom_jst",
-    ["#{test_path}/*.coffee"],
-    template: "#{resources_path}/pagelet.jst"
+  testDoccoRun "custom_jst",
+    ["#{testPath}/*.coffee"],
+    template: "#{resourcesPath}/pagelet.jst"

 # Verify we can use a custom CSS file
 test "custom CSS file", ->
-  test_docco_run "custom_css",
-    ["#{test_path}/*.coffee"],
-    css: "#{resources_path}/pagelet.css"
+  testDoccoRun "custom_css",
+    ["#{testPath}/*.coffee"],
+    css: "#{resourcesPath}/pagelet.css"

 #### Docco Comment Parser

 # Verify we can parse expected comments from each supported language.
 test "single line comment parsing", ->
-  comments_path = path.join test_path, "comments"
+  commentsPath = path.join testPath, "comments"
   options =
-    template: "#{comments_path}/comments.jst"
+    template: "#{commentsPath}/comments.jst"

   # Construct a list of languages to test asynchronously.  It's important
   # that these be tested one at a time, to avoid conflicts between multiple
   # file extensions for a language.  e.g. `c.c` and `c.h` both output to
   # c.html, so they must be run at separate times.
-  language_keys = (ext for ext,l of Docco.languages)
+  languageKeys = (ext for ext,l of Docco.languages)

-  test_next_language = (keys,callback) ->
+  testNextLanguage = (keys,callback) ->
     # We're all done here.
     return callback?() if not keys.length

     ext = keys.shift()
     l = Docco.languages[ext]

-    language_example = path.join comments_path, "#{l.name}#{ext}"
-    language_test    = "comments_test/#{l.name}"
-    language_path    = path.join data_path, language_test
+    languageExample = path.join commentsPath, "#{l.name}#{ext}"
+    languageTest    = "comments_test/#{l.name}"
+    languagePath    = path.join dataPath, languageTest

     # Skip over this language if there is no corresponding test.
-    return test_next_language(keys, callback) if not path.existsSync language_example
+    return testNextLanguage(keys, callback) if not path.existsSync languageExample

     # Run them through docco with the custom `comments.jst` file that
     # outputs a CSV list of doc blocks text.
-    test_docco_run language_test, [language_example], options, ->
+    testDoccoRun languageTest, [languageExample], options, ->

       # Be sure the expected output file exists
-      language_output = path.join language_path, "#{l.name}.html"
-      eq true, path.existsSync(language_output), "#{language_output} -> output file created properly"
+      languageOutput = path.join languagePath, "#{l.name}.html"
+      eq true, path.existsSync(languageOutput), "#{languageOutput} -> output file created properly"

       # Read in the output file contents, split them into a list
       # of comments.
-      content = fs.readFileSync(language_output).toString()
+      content = fs.readFileSync(languageOutput).toString()
       comments = (c.trim() for c in content.split(',') when c.trim() != '')
       eq true, comments.length >= 1, 'expect at least the descriptor comment'

@@ -116,20 +116,20 @@
       expected = parseInt(comments[0])
       eq comments.length, expected, [
         ""
-        "#{path.basename(language_output)} comments"
+        "#{path.basename(languageOutput)} comments"
         "------------------------"
         " expected : #{expected}"
         " found    : #{comments.length}"
       ].join '\n'

       # Invoke the next test
-      test_next_language keys, callback
+      testNextLanguage keys, callback

   # Kick off the first language test.
-  test_next_language language_keys.slice(), ->
+  testNextLanguage languageKeys.slice(), ->
     # Test to be sure block comments are excluded when not explicitly
     # specified.  In this case, the test will check for the existence
     # of only 1 comment in all languages (a single-line)
     options.blocks = false
-    test_next_language language_keys.slice()
+    testNextLanguage languageKeys.slice()


read default open/due from model

read default open/due from model

Needed so that update link goes away after save

--- tutor/src/components/task-plan/builder/tasking-date-times.cjsx
+++ tutor/src/components/task-plan/builder/tasking-date-times.cjsx
@@ -40,6 +40,7 @@
     model = if period then period else Courses.get(courseId)
     _.assign(model, timeChange)
     model.save()
+    @forceUpdate()

   isSetting: ->
     {courseId} = @props
@@ -56,9 +57,12 @@
     TaskingActions.updateTime(id, period?.serialize(), type, value)

   render: ->
-    {isVisibleToStudents, isEditable, period, id, termStart, termEnd} = @props
+    {isVisibleToStudents, isEditable, period, courseId, id, termStart, termEnd} = @props
     period = period.serialize() if period
     commonDateTimesProps = _.pick @props, 'required', 'currentLocale', 'taskingIdentifier'
+
+    model = if period then period else Courses.get(courseId)
+    { default_open_time, default_due_time } = model

     defaults = TaskingStore.getDefaultsForTasking(id, period)
     {open_time, open_date, due_time, due_date} = TaskingStore._getTaskingFor(id, period)
@@ -103,7 +107,7 @@
         setTime={_.partial(@setTime, 'open')}
         value={ openDate }
         defaultValue={open_time or defaults.open_time}
-        defaultTime={defaults.open_time}
+        defaultTime={default_open_time}
         setDefaultTime={@setDefaultTime}
         timeLabel='default_open_time'
         isSetting={@isSetting}
@@ -119,7 +123,7 @@
         setTime={_.partial(@setTime, 'due')}
         value={due_date}
         defaultValue={due_time or defaults.due_time}
-        defaultTime={defaults.due_time}
+        defaultTime={default_due_time}
         setDefaultTime={@setDefaultTime}
         timeLabel='default_due_time'
         isSetting={@isSetting}


Add new attachment

Add new attachment

--- src/checkout.coffee
+++ src/checkout.coffee
@@ -69,7 +69,8 @@
         'giftRegistryData'
         'ratesAndBenefitsData'
         'openTextField'
-        'commercialConditionData'
+        'commercialConditionData',
+        'customData'
       ]

   ###


reattaching menu item click event each render of rowSelector

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -16,7 +16,6 @@
     events:
       "click .js-close-row-selector": "shrink"
       "submit .row__questiontypes__form": "show_picker"
-      "click .questiontypelist__item": "selectMenuItem"
     initialize: (opts)->
       @options = opts
       @ngScope = opts.ngScope
@@ -48,6 +47,7 @@
       @question_name = @line.find('input').val()
       @line.empty()
       $.scrollTo @line, 200, offset: -300
+
       @line.html $viewTemplates.$$render('xlfRowSelector.line', "")
       @line.find('.row__questiontypes__new-question-name').val(@question_name)
       $menu = @line.find(".row__questiontypes__list")
@@ -55,6 +55,7 @@
         menurow = $("<div>", class: "questiontypelist__row").appendTo $menu
         for mitem, i in mrow
           menurow.append $viewTemplates.$$render('xlfRowSelector.cell', mitem.attributes)
+      @$('.questiontypelist__item').click _.bind(@selectMenuItem, @)

     shrink: ->
       # click .js-close-row-selector


calculations can be created post 882 redesign

--- jsapp/xlform_model_view/view.rowSelector.coffee
+++ jsapp/xlform_model_view/view.rowSelector.coffee
@@ -70,9 +70,17 @@

     selectMenuItem: (evt)->
       $('select.skiplogic__rowselect').select2('destroy')
+      rowType = $(evt.target).closest('.questiontypelist__item').data("menuItem")
+      value = @question_name || 'New Question'
+
       rowDetails =
-        type: $(evt.target).closest('.questiontypelist__item').data("menuItem")
-        label: @question_name || 'New Question'
+        type: rowType
+
+      if rowType is 'calculate'
+        rowDetails.calculation = value
+      else
+        rowDetails.label = value
+
       options = {}
       if (rowBefore = @options.spawnedFromView?.model)
         options.after = rowBefore