All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jruby.truffle.stdlib.psych.PsychEmitterNodes Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015, 2017 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 *
 * This code is modified from the Psych JRuby extension module
 * implementation with the following header:
 *
 * Version: EPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Eclipse Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/epl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2010 Charles O Nutter 
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the EPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the EPL, the GPL or the LGPL.
 */
package org.jruby.truffle.stdlib.psych;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.object.DynamicObject;
import org.jcodings.Encoding;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.builtins.CoreClass;
import org.jruby.truffle.builtins.CoreMethod;
import org.jruby.truffle.builtins.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.adapaters.OutputStreamAdapter;
import org.jruby.truffle.core.array.ArrayOperations;
import org.jruby.truffle.core.encoding.EncodingManager;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.Visibility;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.language.objects.AllocateObjectNode;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.emitter.Emitter;
import org.yaml.snakeyaml.emitter.EmitterException;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.ImplicitTuple;
import org.yaml.snakeyaml.events.MappingEndEvent;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.events.SequenceEndEvent;
import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.events.StreamEndEvent;
import org.yaml.snakeyaml.events.StreamStartEvent;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

@CoreClass("Psych::Emitter")
public abstract class PsychEmitterNodes {

    @CoreMethod(names = "allocate", constructor = true)
    public abstract static class AllocateNode extends CoreMethodArrayArgumentsNode {

        @Child private AllocateObjectNode allocateNode = AllocateObjectNode.create();

        @Specialization
        public DynamicObject allocate(DynamicObject rubyClass) {
            return allocateNode.allocate(rubyClass, null, null, null);
        }

    }

    @CoreMethod(names = "initialize", visibility = Visibility.PRIVATE, required = 1, optional = 1)
    public abstract static class InitializeNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject initialize(DynamicObject emitter, DynamicObject io, NotProvided optionsSet) {
            final DumperOptions options = new DumperOptions();
            options.setIndent(2);
            Layouts.PSYCH_EMITTER.setOptions(emitter, options);
            Layouts.PSYCH_EMITTER.setIo(emitter, io);
            return nil();
        }

        @Specialization
        public DynamicObject initialize(
                VirtualFrame frame,
                DynamicObject emitter,
                DynamicObject io,
                DynamicObject optionsSet,
                @Cached("createMethodCall()") CallDispatchHeadNode lineWidthCallNode,
                @Cached("createMethodCall()") CallDispatchHeadNode canonicalCallNode,
                @Cached("createMethodCall()") CallDispatchHeadNode indentationCallNode) {
            final DumperOptions options = new DumperOptions();
            options.setWidth((int) lineWidthCallNode.call(frame, optionsSet, "line_width"));
            options.setCanonical((boolean) canonicalCallNode.call(frame, optionsSet, "canonical"));
            options.setIndent((int) indentationCallNode.call(frame, optionsSet, "indentation"));
            Layouts.PSYCH_EMITTER.setOptions(emitter, options);
            Layouts.PSYCH_EMITTER.setIo(emitter, io);
            return nil();
        }

    }

    @CoreMethod(names = "start_stream", required = 1, lowerFixnum = 1)
    public abstract static class StartStreamNode extends CoreMethodArrayArgumentsNode {

        @TruffleBoundary
        @Specialization
        public DynamicObject startStream(DynamicObject emitter, int encodingOrdinal) {
            if (Layouts.PSYCH_EMITTER.getEmitter(emitter) != null) {
                throw new RaiseException(getContext().getCoreExceptions().runtimeError("already initialized emitter", this));
            }

            final Encoding encoding = YAMLEncoding.values()[encodingOrdinal].getEncoding();
            final Charset charset = EncodingManager.charsetForEncoding(encoding);

            Layouts.PSYCH_EMITTER.setEmitter(emitter,
                    new Emitter(
                            new OutputStreamWriter(
                                    new OutputStreamAdapter(
                                            getContext(),
                                            (DynamicObject) Layouts.PSYCH_EMITTER.getIo(emitter), encoding),
                                    charset),
                    Layouts.PSYCH_EMITTER.getOptions(emitter)));

            emit(emitter, new StreamStartEvent(NULL_MARK, NULL_MARK));

            return emitter;
        }

    }


    @CoreMethod(names = "end_stream")
    public abstract static class EndStreamNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject endStream(DynamicObject emitter) {
            emit(emitter, new StreamEndEvent(NULL_MARK, NULL_MARK));
            return emitter;
        }

    }


    @CoreMethod(names = "start_document", required = 3)
    public abstract static class StartDocumentNode extends CoreMethodArrayArgumentsNode {

        @TruffleBoundary
        @Specialization(guards = {"isRubyArray(version)", "isRubyArray(tags)"})
        public DynamicObject startDocument(DynamicObject emitter, DynamicObject version, DynamicObject tags, boolean implicit) {
            // TODO CS 3-May-16 this method should probably be implemented mainly in Ruby

            DumperOptions.Version optionsVersion = null;

            final Object[] versionArray = ArrayOperations.toObjectArray(version);

            if (versionArray.length == 2) {
                if (!(versionArray[0] instanceof Integer && versionArray[1] instanceof Integer)) {
                    throw new UnsupportedOperationException();
                }

                final int versionInt0 = (int) versionArray[0];
                final int versionInt1 = (int) versionArray[1];

                if (versionInt0 == 1 && versionInt1 == 0) {
                    optionsVersion = DumperOptions.Version.V1_0;
                } else if (versionInt0 == 1 && versionInt1 == 1) {
                    optionsVersion = DumperOptions.Version.V1_1;
                } else {
                    throw new UnsupportedOperationException();
                }
            }

            Map tagsMap = null;

            final Object[] tagsArray = ArrayOperations.toObjectArray(tags);

            if (tagsArray.length > 0) {
                tagsMap = new HashMap<>(tagsArray.length);

                for (int i = 0; i < tagsArray.length; i++) {
                    final Object[] tagsTuple = ArrayOperations.toObjectArray((DynamicObject) tagsArray[i]);

                    if (tagsTuple.length != 2) {
                        throw new UnsupportedOperationException();
                    }

                    tagsMap.put(tagsTuple[0].toString(), tagsTuple[1].toString());
                }
            }

            emit(emitter, new DocumentStartEvent(NULL_MARK, NULL_MARK, !implicit, optionsVersion, tagsMap));

            return emitter;
        }

    }

    @CoreMethod(names = "end_document", required = 1)
    public abstract static class EndDocumentNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject endDocument(DynamicObject emitter, boolean implicit) {
            emit(emitter, new DocumentEndEvent(NULL_MARK, NULL_MARK, !implicit));
            return emitter;
        }

    }

    @CoreMethod(names = "scalar", required = 6, lowerFixnum = 6)
    public abstract static class ScalarNode extends CoreMethodArrayArgumentsNode {

        // Map style constants from Psych values (ANY = 0 ... FOLDED = 5)
        // to SnakeYaml values; see psych/nodes/scalar.rb.
        private static final Character[] SCALAR_STYLES = new Character[] {
                null, // ANY; we'll choose plain
                null, // PLAIN
                '\'', // SINGLE_QUOTED
                '"',  // DOUBLE_QUOTED
                '|',  // LITERAL
                '>',  // FOLDED
        };

        @TruffleBoundary
        @Specialization(guards = "isRubyString(value)")
        public DynamicObject scalar(
                DynamicObject emitter,
                DynamicObject value,
                Object anchor,
                Object tag,
                boolean plain,
                boolean quoted,
                int style) {
            final String anchorString;

            if (isNil(anchor)) {
                anchorString = null;
            } else {
                anchorString = anchor.toString();
            }

            final String tagString;

            if (isNil(tag)) {
                tagString = null;
            } else {
                tagString = tag.toString();
            }

            emit(emitter, new ScalarEvent(
                    anchorString,
                    tagString,
                    new ImplicitTuple(plain, quoted),
                    value.toString(),
                    NULL_MARK,
                    NULL_MARK,
                    SCALAR_STYLES[style]));

            return emitter;
        }

    }


    @CoreMethod(names = "start_sequence", required = 4, lowerFixnum = 4)
    public abstract static class StartSequenceNode extends CoreMethodArrayArgumentsNode {

        private static final int SEQUENCE_BLOCK = 1;

        @TruffleBoundary
        @Specialization
        public DynamicObject startSequence(
                DynamicObject emitter,
                Object anchor,
                Object tag,
                boolean implicit,
                int style) {
            final String anchorString;

            if (isNil(anchor)) {
                anchorString = null;
            } else {
                anchorString = anchor.toString();
            }

            final String tagString;

            if (isNil(tag)) {
                tagString = null;
            } else {
                tagString = tag.toString();
            }

            emit(emitter, new SequenceStartEvent(
                    anchorString,
                    tagString,
                    implicit,
                    NULL_MARK,
                    NULL_MARK,
                    style != SEQUENCE_BLOCK));

            return emitter;
        }

    }

    @CoreMethod(names = "end_sequence")
    public abstract static class EndSequenceNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject endSequence(DynamicObject emitter) {
            emit(emitter, new SequenceEndEvent(NULL_MARK, NULL_MARK));
            return emitter;
        }

    }

    @CoreMethod(names = "start_mapping", required = 4, lowerFixnum = 4)
    public abstract static class StartMappingNode extends CoreMethodArrayArgumentsNode {

        private static final int MAPPING_BLOCK = 1;

        @TruffleBoundary
        @Specialization
        public DynamicObject startMapping(
                DynamicObject emitter,
                Object anchor,
                Object tag,
                boolean implicit,
                int style) {
            final String anchorString;

            if (isNil(anchor)) {
                anchorString = null;
            } else {
                anchorString = anchor.toString();
            }

            final String tagString;

            if (isNil(tag)) {
                tagString = null;
            } else {
                tagString = tag.toString();
            }

            emit(emitter, new MappingStartEvent(
                    anchorString,
                    tagString,
                    implicit,
                    NULL_MARK,
                    NULL_MARK,
                    style != MAPPING_BLOCK));

            return emitter;
        }

    }

    @CoreMethod(names = "end_mapping")
    public abstract static class EndMappingNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject endMapping(DynamicObject emitter) {
            emit(emitter, new MappingEndEvent(NULL_MARK, NULL_MARK));
            return emitter;
        }

    }

    @CoreMethod(names = "alias", required = 1)
    public abstract static class AliasNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public DynamicObject alias(DynamicObject emitter, Object anchor) {
            emit(emitter, new AliasEvent(anchor.toString(), NULL_MARK, NULL_MARK));
            return emitter;
        }

    }

    @CoreMethod(names = "canonical=", required = 1)
    public abstract static class SetCanonicalNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public boolean setCanonical(DynamicObject emitter, boolean canonical) {
            Layouts.PSYCH_EMITTER.getOptions(emitter).setCanonical(canonical);
            return canonical;
        }

    }

    @CoreMethod(names = "canonical")
    public abstract static class CanonicalNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public boolean canonical(DynamicObject emitter) {
            return Layouts.PSYCH_EMITTER.getOptions(emitter).isCanonical();
        }

    }

    @CoreMethod(names = "indentation=", required = 1, lowerFixnum = 1)
    public abstract static class SetIndentationNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public int setIndentation(DynamicObject emitter, int indentation) {
            Layouts.PSYCH_EMITTER.getOptions(emitter).setIndent(indentation);
            return indentation;
        }

    }

    @CoreMethod(names = "indentation")
    public abstract static class IndentationNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public int indentation(DynamicObject emitter) {
            return Layouts.PSYCH_EMITTER.getOptions(emitter).getIndent();
        }

    }

    @CoreMethod(names = "line_width=", required = 1, lowerFixnum = 1)
    public abstract static class SetLineWidthNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public int setLineWidth(DynamicObject emitter, int width) {
            Layouts.PSYCH_EMITTER.getOptions(emitter).setWidth(width);
            return width;
        }

    }

    @CoreMethod(names = "line_width")
    public abstract static class LineWidthNode extends CoreMethodArrayArgumentsNode {

        @Specialization
        public int lineWidth(DynamicObject emitter) {
            return Layouts.PSYCH_EMITTER.getOptions(emitter).getWidth();
        }

    }

    @TruffleBoundary
    private static void emit(DynamicObject emitter, Event event) {
        try {
            if (Layouts.PSYCH_EMITTER.getEmitter(emitter) == null) {
                throw new UnsupportedOperationException();
            }

            Layouts.PSYCH_EMITTER.getEmitter(emitter).emit(event);
        } catch (IOException ioe) {
            throw new UnsupportedOperationException();
        } catch (EmitterException ee) {
            throw new UnsupportedOperationException();
        }
    }

    private static final Mark NULL_MARK = new Mark(null, 0, 0, 0, null, 0);

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy