Skip to content

Commit

Permalink
Add AST manipulations (#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
NightFlyer authored and KristopherGBaker committed Jun 3, 2019
1 parent 02a351b commit 9de80c4
Show file tree
Hide file tree
Showing 4 changed files with 508 additions and 4 deletions.
10 changes: 10 additions & 0 deletions Maaku.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
0453B9A122763BAA00AB31CC /* TasklistItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453B9A022763BAA00AB31CC /* TasklistItemSpec.swift */; };
0453B9A222763BAA00AB31CC /* TasklistItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453B9A022763BAA00AB31CC /* TasklistItemSpec.swift */; };
0453B9A322763BAA00AB31CC /* TasklistItemSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0453B9A022763BAA00AB31CC /* TasklistItemSpec.swift */; };
04A3A74A229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */; };
04A3A74B229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */; };
04A3A74C229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */; };
04A3A74D229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */; };
04C2EF4D2297251700413346 /* CMParserSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C2EF4B229724D900413346 /* CMParserSpec.swift */; };
04C2EF4E2297251800413346 /* CMParserSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C2EF4B229724D900413346 /* CMParserSpec.swift */; };
04C2EF4F2297251B00413346 /* CMParserSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C2EF4B229724D900413346 /* CMParserSpec.swift */; };
Expand Down Expand Up @@ -356,6 +360,7 @@
0453B98E22760C4C00AB31CC /* CMNodeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CMNodeSpec.swift; sourceTree = "<group>"; };
0453B99822760CFA00AB31CC /* TasklistItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasklistItem.swift; sourceTree = "<group>"; };
0453B9A022763BAA00AB31CC /* TasklistItemSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TasklistItemSpec.swift; sourceTree = "<group>"; };
04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMNode+ASTManipulation.swift"; sourceTree = "<group>"; };
04C2EF4B229724D900413346 /* CMParserSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMParserSpec.swift; sourceTree = "<group>"; };
04C2EF502299E40D00413346 /* DocumentConverterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentConverterSpec.swift; sourceTree = "<group>"; };
04C93A9B226F89440058589D /* CMExtensionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CMExtensionSpec.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -729,6 +734,7 @@
A288C44A1FEF29AA00667F4A /* CMIterator.swift */,
A288C4511FEF29AA00667F4A /* CMListType.swift */,
A288C44F1FEF29AA00667F4A /* CMNode.swift */,
04A3A749229F24A700ACBC09 /* CMNode+ASTManipulation.swift */,
90D97E99229687B800067FF7 /* CMNode+Render.swift */,
90D97E9B229687EF00067FF7 /* CMNode+Task.swift */,
A288C44C1FEF29AA00667F4A /* CMNodeType.swift */,
Expand Down Expand Up @@ -1454,6 +1460,7 @@
buildActionMask = 2147483647;
files = (
A288C4971FEF29AB00667F4A /* CMParser.swift in Sources */,
04A3A74B229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */,
A288C4D71FEF29AB00667F4A /* InlineCode.swift in Sources */,
A288C4CB1FEF29AB00667F4A /* Strikethrough.swift in Sources */,
90D97EA02296880300067FF7 /* CMNode+Task.swift in Sources */,
Expand Down Expand Up @@ -1553,6 +1560,7 @@
buildActionMask = 2147483647;
files = (
A288C4981FEF29AB00667F4A /* CMParser.swift in Sources */,
04A3A74C229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */,
A288C4D81FEF29AB00667F4A /* InlineCode.swift in Sources */,
A288C4CC1FEF29AB00667F4A /* Strikethrough.swift in Sources */,
90D97EA12296880400067FF7 /* CMNode+Task.swift in Sources */,
Expand Down Expand Up @@ -1607,6 +1615,7 @@
buildActionMask = 2147483647;
files = (
A288C4991FEF29AB00667F4A /* CMParser.swift in Sources */,
04A3A74D229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */,
A288C4D91FEF29AB00667F4A /* InlineCode.swift in Sources */,
A288C4CD1FEF29AB00667F4A /* Strikethrough.swift in Sources */,
90D97EA22296880500067FF7 /* CMNode+Task.swift in Sources */,
Expand Down Expand Up @@ -1706,6 +1715,7 @@
buildActionMask = 2147483647;
files = (
A288C4961FEF29AB00667F4A /* CMParser.swift in Sources */,
04A3A74A229F24A700ACBC09 /* CMNode+ASTManipulation.swift in Sources */,
A288C4D61FEF29AB00667F4A /* InlineCode.swift in Sources */,
A288C4CA1FEF29AB00667F4A /* Strikethrough.swift in Sources */,
90D97E9C229687EF00067FF7 /* CMNode+Task.swift in Sources */,
Expand Down
220 changes: 220 additions & 0 deletions Sources/Maaku/CMark/CMNode+ASTManipulation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//
// CMNode+ASTManipulation.swift
// Maaku
//
// Created by Tim Learmont on 5/29/19.
// Copyright © 2019 Kristopher Baker. All rights reserved.
//

import Foundation
import libcmark_gfm

/// Extension for manipulating ndoe values and the Abstract Syntax Tree
public extension CMNode {
enum ASTError: Error {
case canNotSetValue
case canNotInsert
case documentMismatch
}

func setStringValue(_ newValue: String) throws {
// cmark_node_set_literal copies the string
if cmark_node_set_literal(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setHeadingLevel(_ newValue: Int32) throws {
if cmark_node_set_heading_level(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setFencedCodeInfo(_ newValue: String) throws {
// cmark_node_set_fence_info copies the string
if cmark_node_set_fence_info(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setCustomOnEnter(_ newValue: String) throws {
// cmark_node_set_on_enter copies the string
if cmark_node_set_on_enter(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setCustomOnExit(_ newValue: String) throws {
// cmark_node_set_on_exit copies the string
if cmark_node_set_on_exit(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setListType(_ newValue: CMListType) throws {
if cmark_node_set_list_type(cmarkNode, cmark_list_type(newValue.rawValue)) != 1 {
throw ASTError.canNotSetValue
}
}

func setListDelimiterType(_ newValue: CMDelimiterType) throws {
if cmark_node_set_list_delim(cmarkNode, cmark_delim_type(newValue.rawValue)) != 1 {
throw ASTError.canNotSetValue
}
}

func setListStartingNumber(_ newValue: Int32) throws {
if cmark_node_set_list_start(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setListTight(_ newValue: Bool) throws {
if cmark_node_set_list_tight(cmarkNode, newValue ? 1 : 0) != 1 {
throw ASTError.canNotSetValue
}
}

func setDestination(_ newValue: String) throws {
// cmark_node_set_url copies the string
if cmark_node_set_url(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setURL(_ newValue: URL) throws {
try setDestination(newValue.absoluteString)
}

func setTitle(_ newValue: String) throws {
// cmark_node_set_title copies the string
if cmark_node_set_title(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

func setLiteral(_ newValue: String) throws {
// cmark_node_set_literal copies the string
if cmark_node_set_literal(cmarkNode, newValue) != 1 {
throw ASTError.canNotSetValue
}
}

/// Inserts this node into the AST before the given node
///
/// - Parameters:
/// - beforeNode: the node that this node will be inserted before.
///
func insertIntoTree(beforeNode: CMNode) throws {
// There should only be one node at the top level (the Document node),
// so if node we're trying to add before is the document node, don't allow.
// We also don't allow mutliple document nodes, so that's an error, too
guard (beforeNode.type != .document) && (self.type != .document) else {
throw ASTError.canNotInsert
}
// For now, we only allow nodes in the same memory management set to play together.
guard (beforeNode.internalMemoryOwner != nil)
&& (self.internalMemoryOwner != nil)
&& (beforeNode.internalMemoryOwner!.cmarkNode == self.internalMemoryOwner!.cmarkNode) else {
throw ASTError.documentMismatch
}
if cmark_node_insert_before(beforeNode.cmarkNode, self.cmarkNode) != 1 {
throw ASTError.canNotInsert
}
}

/// Inserts this node into the AST after the given node
///
/// - Paremeters:
/// - afterNode: the node that this node will be inserted after.
func insertIntoTree(afterNode: CMNode) throws {
// There should only be one node at the top level (the Document node),
// so if node we're trying to add before is the document node, don't allow.
// We also don't allow mutliple document nodes, so that's an error, too
guard (afterNode.type != .document) && (self.type != .document) else {
throw ASTError.canNotInsert
}
// For now, we only allow nodes in the same memory management set to play together.
guard (afterNode.internalMemoryOwner != nil)
&& (self.internalMemoryOwner != nil)
&& (afterNode.internalMemoryOwner!.cmarkNode == self.internalMemoryOwner!.cmarkNode) else {
throw ASTError.documentMismatch
}
if cmark_node_insert_after(afterNode.cmarkNode, self.cmarkNode) != 1 {
throw ASTError.canNotInsert
}
}

/// Inserts this node into the AST as the first child of the given node.
///
/// All previous children of the given node will come after this node.
///
/// - Paremeters:
/// - asFirstChildOf: the node that this node will be inserted after.
func insertIntoTree(asFirstChildOf parent: CMNode) throws {
// We don't allow mutliple document nodes, so if the node we're trying to insert is a doc node,
// that's an error.
guard self.type != .document else {
throw ASTError.canNotInsert
}
// For now, we only allow nodes in the same memory management set to play together.
// The test is a bit complicated, because parent might be the referencedMemoryOwner.
guard (self.internalMemoryOwner === parent)
|| ((parent.internalMemoryOwner != nil)
&& (self.internalMemoryOwner === parent.internalMemoryOwner)) else {
throw ASTError.documentMismatch
}
if cmark_node_prepend_child(parent.cmarkNode, self.cmarkNode) != 1 {
throw ASTError.canNotInsert
}
}

/// Inserts this node into the AST as the last child of the given node.
///
/// All previous children of the given node will come before this node
///
/// - Paremeters:
/// - afterNode: the node that this node will be inserted after.
func insertIntoTree(asLastChildOf parent: CMNode) throws {
// We don't allow mutliple document nodes, so if the node we're trying to insert is a doc node,
// that's an error.
guard self.type != .document else {
throw ASTError.canNotInsert
}
// For now, we only allow nodes in the same memory management set to play together.
// The test is a bit complicated, because parent might be the referencedMemoryOwner.
guard (self.internalMemoryOwner === parent)
|| ((parent.internalMemoryOwner != nil)
&& (self.internalMemoryOwner === parent.internalMemoryOwner)) else {
throw ASTError.documentMismatch
}
if cmark_node_append_child(parent.cmarkNode, self.cmarkNode) != 1 {
throw ASTError.canNotInsert
}
}

/// Create a new node and add as the last child of the given parent
///
/// - Parameters:
/// - type: the type of the node to be created
/// - extension: the extension for this node (or nil if this type is a base type).
/// - parent: the node that will be the parent of the newly created node.
/// This is only needed because our current memory management scheme requires
/// all nodes to be owned by a parent node. Any nodes without parents might
/// end up having their data freed incorrectly.
convenience init?(type: CMNodeType, `extension`: CMExtensionOption? = nil, parent: CMNode) {
let node: UnsafeMutablePointer<cmark_node>
if `extension` != nil {
node = cmark_node_new_with_ext(cmark_node_type(type.rawValue), `extension`!.syntaxExtension)
} else {
node = cmark_node_new(cmark_node_type(type.rawValue))
}
self.init(cmarkNode: node, memoryOwner: parent)

do {
try insertIntoTree(asLastChildOf: parent)
} catch {
return nil
}
}
}
10 changes: 7 additions & 3 deletions Sources/Maaku/CMark/CMNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ public class CMNode {
private let referencedMemoryOwner: CMNode?

/// Read-only access to the memory owner
/// Its primary purpose is to allow tests to verify that the right
/// things are going on.
/// This allows extensions (or tests) to query the value.
var internalMemoryOwner: CMNode? {
return referencedMemoryOwner
}
Expand Down Expand Up @@ -56,7 +55,12 @@ public class CMNode {
deinit {
if referencedMemoryOwner == nil {
// We're the one that really owns the memory, so free it.
cmark_node_free(cmarkNode)
// But only free it if the node doesn't have a parent!
// If it has a parent, then we either need to remove it from the parent
// or not free it, because the parent will free it.
if cmarkNode.pointee.parent == nil {
cmark_node_free(cmarkNode)
}
}
}
}
Expand Down
Loading

0 comments on commit 9de80c4

Please sign in to comment.