function generateDoc (name, code) {
  // get block comment from code
  const commentRegex = /\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g
  // const match = commentRegex.exec(code)

  const comments = findAll(code, commentRegex).map(match => getCommentContents(match[0]))

  // Find the right comment.
  // First search a comment containing the text "Syntax:" and "Examples:".
  // If not found, select the first comment
  const comment = comments.find(comment => {
    return /\n *syntax: *\n/i.exec(comment) && /\n *examples: *\n/i.exec(comment)
  }) || comments[0]

  if (!comment) {
    return null
  }

  // get text content inside block comment
  function getCommentContents (comment) {
    return comment.replace('/**', '')
      .replace('*/', '')
      .replace(/\n\s*\* ?/g, '\n')
      .replace(/\r/g, '')
  }

  const lines = comment.split('\n')
  let line = ''

  // get next line
  function next () {
    line = lines.shift()
  }

  // returns true if current line is empty
  function empty () {
    return !line || !line.trim()
  }

  // returns true if there still is a current line
  function exists () {
    return line !== undefined
  }

  // returns true if current line is a header like 'Syntax:'
  function isHeader () {
    return /^(Name|Syntax|Description|Examples|See also)/i.test(line)
  }

  // returns true if the current line starts with an annotation like @param
  function isAnnotation () {
    return /^@/.test(line)
  }

  function skipEmptyLines () {
    while (exists() && empty()) next()
  }

  function stripLeadingSpaces (lines) {
    let spaces = null
    lines.forEach(function (line) {
      const match = /^ +/.exec(line)
      const s = match && match[0] && match[0].length
      if (s > 0 && (spaces === null || s < spaces)) {
        spaces = s
      }
    })

    if (spaces) {
      lines.forEach(function (line, index) {
        lines[index] = line.substring(spaces)
      })
    }
  }

  function parseDescription () {
    let description = ''

    while (exists() && !isHeader() && !isAnnotation()) {
      description += line + '\n'

      next()
    }

    // remove trailing returns
    while (description.charAt(description.length - 1) === '\n') {
      description = description.substring(0, description.length - 1)
    }

    doc.description = description
  }

  function parseSyntax () {
    if (/^syntax/i.test(line)) {
      next()
      skipEmptyLines()

      while (exists() && !empty()) {
        doc.syntax.push(line)
        next()
      }
      stripLeadingSpaces(doc.syntax)

      skipEmptyLines()

      return true
    }
    return false
  }

  function parseWhere () {
    if (/^where/i.test(line)) {
      next()
      skipEmptyLines()

      while (exists() && !empty()) {
        doc.where.push(line)
        next()
      }

      skipEmptyLines()

      return true
    }
    return false
  }

  function parseExamples () {
    if (/^example/i.test(line)) {
      next()
      skipEmptyLines()

      while (exists() && (empty() || line.charAt(0) === ' ')) {
        doc.examples.push(line)
        next()
      }
      stripLeadingSpaces(doc.examples)

      if (doc.examples.length > 0 && doc.examples[doc.examples.length - 1].trim() === '') {
        doc.examples.pop()
      }

      skipEmptyLines()

      return true
    }
    return false
  }

  function parseSeeAlso () {
    if (/^see also/i.test(line)) {
      next()
      skipEmptyLines()

      while (exists() && !empty()) {
        const names = line.split(',')
        doc.seeAlso = doc.seeAlso.concat(names.map(function (name) {
          return name.trim()
        }))
        next()
      }

      skipEmptyLines()

      return true
    }
    return false
  }

  function trim (text) {
    return text.trim()
  }

  // replace characters like '<' with HTML entities like '&lt;'
  function escapeTags (text) {
    return text.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  }

  function parseParameters () {
    let count = 0
    let match
    do {
      match = /\s*@param\s*\{(.*)}\s*\[?(\w*)]?\s*(.*)?$/.exec(line)
      if (match) {
        next()

        count++
        const annotation = {
          name: match[2] || '',
          description: (match[3] || '').trim(),
          types: match[1].split('|').map(trim).map(escapeTags)
        }
        doc.parameters.push(annotation)

        // TODO: this is an ugly hack to extract the default value
        const index = annotation.description.indexOf(']')
        let defaultValue = null
        if (index !== -1) {
          defaultValue = annotation.description.substring(1, index).trim()
          annotation.description = annotation.description.substring(index + 1).trim()
        }

        // multi line description (must be non-empty and not start with @param or @return)
        while (exists() && !empty() && !/^\s*@/.test(line)) {
          const lineTrim = line.trim()
          const separator = (lineTrim[0] === '-' ? '</br>' : ' ')
          annotation.description += separator + lineTrim
          next()
        }

        if (defaultValue !== null) {
          annotation.description += ' Default value: ' + defaultValue + '.'
        }
      }
    } while (match)

    return count > 0
  }

  function parseThrows () {
    let count = 0
    let match
    do {
      match = /\s*@throws\s*\{(.*)}\s*(.*)?$/.exec(line)
      if (match) {
        next()

        count++
        const annotation = {
          description: (match[2] || '').trim(),
          type: (match[1] || '').trim()
        }
        doc.mayThrow.push(annotation)

        // multi line description (must be non-empty and not start with @param or @return)
        while (exists() && !empty() && !/^\s*@/.test(line)) {
          const lineTrim = line.trim()
          const separator = (lineTrim[0] === '-' ? '</br>' : ' ')
          annotation.description += separator + lineTrim
          next()
        }
      }
    } while (match)

    return count > 0
  }

  function parseReturns () {
    const match = /\s*@returns?\s*\{(.*)}\s*(.*)?$/.exec(line)
    if (match) {
      next()

      doc.returns = {
        description: match[2] || '',
        types: match[1].split('|').map(trim).map(escapeTags)
      }

      // multi line description
      while (exists() && !empty() && !/^\s*@/.test(line)) {
        doc.returns.description += ' ' + line.trim()
        next()
      }

      return true
    }
    return false
  }

  // initialize doc
  const doc = {
    name: name,
    description: '',
    syntax: [],
    where: [],
    examples: [],
    seeAlso: [],
    parameters: [],
    returns: null,
    mayThrow: []
  }

  next()
  skipEmptyLines()
  parseDescription()

  do {
    skipEmptyLines()

    const handled = parseSyntax() ||
        parseWhere() ||
        parseExamples() ||
        parseSeeAlso() ||
        parseParameters() ||
        parseReturns() ||
        parseThrows()

    if (!handled) {
      // skip this line, no one knows what to do with it
      next()
    }
  } while (exists())

  return doc
}
Extract JSON documentation from the comments in a file with JavaScript code
@param {String} name    Function name
@param {String} code    javascript code containing a block comment
                        describing a math.js function
@return {Object} doc    json document