সমীকরণ (অভিব্যক্তি) অগ্রগতির সাথে পার্সার?


104

আমি একটি সাধারণ স্ট্যাক অ্যালগরিদম ব্যবহার করে একটি সমীকরণ পার্সার বিকাশ করেছি যা বাইনারি (+, -, |, &, *, /, ইত্যাদি) অপারেটর, অ্যানারি (!) অপারেটর এবং প্রথম বন্ধনী পরিচালনা করবে।

তবে এই পদ্ধতিটি ব্যবহার করে আমাকে একই নজিরযুক্ত সমস্ত কিছু রেখে দেয় - এটি অপারেটর নির্বিশেষে বাম থেকে ডানদিকে মূল্যায়ন করা হয়, যদিও প্যারেনেসিস ব্যবহার করে অগ্রাধিকার প্রয়োগ করা যেতে পারে।

সুতরাং এখনই "1 + 11 * 5" 60 প্রত্যাবর্তন করে, কেউ প্রত্যাশা হিসাবে 56 নয়।

যদিও এটি বর্তমান প্রকল্পের জন্য উপযুক্ত, আমি চাই যে সাধারণ উদ্দেশ্যগুলি আমি পরবর্তী প্রকল্পগুলির জন্য ব্যবহার করতে পারি।

স্বচ্ছতার জন্য সম্পাদিত:

অগ্রাধিকার সহ সমীকরণ পার্সিংয়ের জন্য একটি ভাল অ্যালগরিদম কী?

আমি প্রয়োগ করার জন্য সহজ কিছুতে আগ্রহী এবং বুঝতে পারি যে উপলব্ধ কোড সহ লাইসেন্স সংক্রান্ত সমস্যাগুলি এড়াতে আমি নিজেকে কোড করতে পারি।

ব্যাকরণ:

ব্যাকরণ প্রশ্নটি আমি বুঝতে পারি না - আমি এটি হাতে হাতে লিখেছি। এটি যথেষ্ট সহজ যে আমি ওয়াইএসিসি বা বাইসনের প্রয়োজনীয়তা দেখতে পাচ্ছি না। আমার কেবল "2 + 3 * (42/13)" সমীকরণের সাথে স্ট্রিংগুলি গণনা করা দরকার।

ভাষা:

আমি সি তে এটি করছি, তবে আমি কোনও ভাষা নির্দিষ্ট সমাধান নয়, একটি অ্যালগরিদমে আগ্রহী। সি যথেষ্ট নিম্ন স্তরের যে প্রয়োজন দেখা দিলে অন্য ভাষায় রূপান্তর করা সহজ হবে।

কোড উদাহরণ

আমি উপরে যে সাধারণ এক্সপ্রেশন পার্সারটির কথা বলছিলাম তার পরীক্ষার কোড পোস্ট করেছি। প্রকল্পের প্রয়োজনীয়তা পরিবর্তিত হয়েছে এবং তাই প্রকল্পের সাথে সংযুক্ত না হওয়ায় আমার কখনই পারফরম্যান্স বা স্থানের জন্য কোডটি অনুকূল করতে হবে না। এটি মূল ভার্বোজ ফর্মের, এবং সহজেই বোধগম্য হওয়া উচিত। অপারেটর অগ্রাধিকারের ক্ষেত্রে যদি আমি এর সাথে আরও কিছু করি তবে আমি সম্ভবত ম্যাক্রো হ্যাকটি বেছে নেব কারণ এটি বাকি প্রোগ্রামটির সাথে সরলতার সাথে মেলে। আমি যদি কখনও সত্যিকারের প্রকল্পে এটি ব্যবহার করি তবে আমি আরও কমপ্যাক্ট / দ্রুত পার্সার যাব।

সম্পর্কিত প্রশ্ন

গণিতের পার্সারের স্মার্ট ডিজাইন?

-Adam


আমি আমার ব্লগে সি # তে একটি এক্সপ্রেশন পার্সার লিখেছি । এটি শান্টিং ইয়ার্ড অ্যালগরিদমের স্ট্যাক ছাড়াই পোস্টফিক্সে ইনফিক্স করে। এটি কেবল একটি অ্যারে ব্যবহার করে।
গুগ

যেহেতু আমি বুঝতে পেরেছি আপনি কেবল গাণিতিক এক্সপ্রেশন পার্স প্রয়োজন। বিপরীত পোলিশ স্বরলিপি
Mishadoff

উত্তর:


69

কঠিন পথে

আপনি একটি পুনরাবৃত্তিক বংশদ্ভুত পার্সার চান

অগ্রাধিকার পাওয়ার জন্য আপনাকে পুনরাবৃত্তভাবে চিন্তা করতে হবে, উদাহরণস্বরূপ, আপনার নমুনা স্ট্রিংটি ব্যবহার করে,

1+11*5

ম্যানুয়ালি এটি করতে, আপনাকে পড়তে হবে 1, তারপরে যোগটি দেখতে হবে এবং শুরু করে একটি সম্পূর্ণ নতুন পুনরাবৃত্ত পার্স "সেশন" শুরু 11করতে হবে ... এবং 11 * 5তার নিজস্ব ফ্যাক্টারে পার্স করা নিশ্চিত করুন , যার সাথে একটি পার্স গাছ পাওয়া যাবে 1 + (11 * 5)

এটি সমস্ত ব্যাখ্যা করার চেষ্টা করার পরেও বেদনাদায়ক বোধ করে, বিশেষত সি'র যুক্ত হওয়া শক্তিহীনতার সাথে দেখুন 11 টি বিশ্লেষণের পরে, যদি * আসলে + এর পরিবর্তে + হয়, আপনাকে একটি শব্দ তৈরির চেষ্টাটি ত্যাগ করে পরিবর্তে পার্স করতে হবে 11নিজেকে একটি কারণ হিসাবে। আমার মাথা ইতিমধ্যে বিস্ফোরিত হয়। এটি পুনরাবৃত্তিমূলক শালীন কৌশল দ্বারা সম্ভব, তবে আরও একটি ভাল উপায় আছে ...

সহজ (ডান) উপায়

আপনি যদি বাইসনের মতো জিপিএল সরঞ্জাম ব্যবহার করেন তবে সম্ভবত লাইসেন্স সংক্রান্ত বিষয়ে আপনার চিন্তা করার দরকার নেই যেহেতু বাইসনের মাধ্যমে উত্পন্ন সি কোডটি জিপিএল দ্বারা আচ্ছাদিত হয়নি (আইএনএএল তবে আমি নিশ্চিত যে জিপিএল সরঞ্জামগুলি জিপিএলকে জোর করে না উত্পন্ন কোড / বাইনারিস; উদাহরণস্বরূপ অ্যাপল জিসিসির সাথে অ্যাপারচার বলে এবং কোডটি জিপিএল কোড ছাড়াই এটি বিক্রয় করে) কোডগুলি কম্পাইল করে।

বাইসন ডাউনলোড করুন (বা সমমানের কিছু, এএনটিএলআর ইত্যাদি) ডাউনলোড করুন।

সাধারণত কিছু নমুনা কোড থাকে যা আপনি কেবল বাইসন চালাতে এবং আপনার পছন্দসই সি কোডটি পেতে পারেন যা এই চারটি ফাংশন ক্যালকুলেটরটি দেখায়:

http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html

উত্পন্ন কোডটি দেখুন এবং দেখুন এটি যতটা সহজ শোনাচ্ছে তত সহজ নয়। এছাড়াও, বাইসনের মতো কোনও সরঞ্জাম ব্যবহারের সুবিধাগুলি হ'ল 1) আপনি কিছু শিখুন (বিশেষত যদি আপনি ড্রাগন বইটি পড়ে থাকেন এবং ব্যাকরণ সম্পর্কে শিখেন), 2) আপনি চক্রটি পুনরায় উদ্ভাবনের চেষ্টা করে এনআইএইচ এড়ান । একটি আসল পার্সার-জেনারেটর সরঞ্জামের সাহায্যে আপনার কাছে পরে আরও বড় হওয়ার আশা রয়েছে, আপনি জানেন এমন অন্যান্য লোককে দেখিয়ে যা পার্সারগুলি পার্সিং সরঞ্জামগুলির ডোমেন।


হালনাগাদ:

এখানকার লোকেরা প্রচুর পরামর্শ দিয়েছেন। পার্সিং সরঞ্জামগুলি এড়িয়ে যাওয়া বা কেবল শান্টিং ইয়ার্ড অ্যালগরিদম বা একটি হাত ঘূর্ণিত পুনরাবৃত্তিক শালীন পার্সার ব্যবহারের বিরুদ্ধে আমার কেবল সতর্কতা হ'ল এই ছোট খেলনা ভাষা 1 ফাংশন (পাপ, কোস, লগ) এবং ভেরিয়েবল, শর্ত এবং এর সাথে বড় বাস্তব ভাষায় রূপান্তর করতে পারে লুপ।

একটি ছোট, সাধারণ দোভাষীর জন্য ফ্লেক্স / বাইসন খুব ভালভাবে ওভারকিল হতে পারে তবে পরিবর্তনগুলি করা দরকার বা বৈশিষ্ট্যগুলি যুক্ত করার প্রয়োজন হলে একটি পার্সার + মূল্যায়নকারী লাইনটিতে সমস্যা সৃষ্টি করতে পারে। আপনার পরিস্থিতি পরিবর্তিত হবে এবং আপনাকে আপনার রায় ব্যবহার করতে হবে; কেবলমাত্র আপনার পাপের জন্য অন্য ব্যক্তিকে শাস্তি দেবেন না [2] এবং পর্যাপ্ত সরঞ্জামের চেয়ে কম তৈরি করুন।

পার্সিংয়ের জন্য আমার প্রিয় সরঞ্জাম

কাজের জন্য বিশ্বের সেরা সরঞ্জামটি পার্সেক গ্রন্থাগার (পুনরাবৃত্ত শালীন পার্সারদের জন্য) যা প্রোগ্রামিং ভাষা হাস্কেলের সাথে আসে। এটি দেখতে অনেকটা বিএনএফ এর মতো , বা পার্সিংয়ের জন্য কিছু বিশেষ সরঞ্জাম বা ডোমেন নির্দিষ্ট ভাষার মতো (নমুনা কোড [3]), তবে এটি আসলে হাস্কেলের একটি নিয়মিত গ্রন্থাগার, যার অর্থ এটি বাকীগুলির মতো একই বিল্ড স্টেপে সংকলিত হয়েছে আপনার হাস্কেল কোডটি রয়েছে এবং আপনি নির্বিচারে হাস্কেল কোডটি লিখতে পারেন এবং আপনার পার্সারের মধ্যে কল করতে পারেন এবং আপনি একই কোডে অন্য লাইব্রেরিগুলিকে মিশ্রিত করতে এবং মিলাতে পারেন । (হাস্কেল ব্যতীত অন্য ভাষায় এর মতো পার্সিংয়ের ভাষা এম্বেডিংয়ের ফলে সিনট্যাকটিক ক্রাফ্টের বোঝা বোঝা যায় way আমি সি-তে এটি করেছি এবং এটি বেশ ভালভাবে কাজ করে তবে এটি এত সুন্দর এবং সংযোগ নয়))

মন্তব্য:

1 রিচার্ড স্টলম্যান বলেছেন, কেন আপনাকে টিসিএল ব্যবহার করা উচিত নয়

ইমাক্সের মূল পাঠটি হ'ল এক্সটেনশনের ভাষাটি কেবল "সম্প্রসারণের ভাষা" হওয়া উচিত নয়। এটি একটি আসল প্রোগ্রামিং ল্যাঙ্গুয়েজ হওয়া উচিত, যা প্রচুর প্রোগ্রাম লেখার জন্য এবং রক্ষণাবেক্ষণের জন্য তৈরি করা হয়েছিল। কারণ মানুষ তা করতে চাইবে!

[2] হ্যাঁ, আমি এই "ভাষা" ব্যবহার করে চিরতরে দাগ পড়েছি।

এছাড়াও নোট করুন যে আমি যখন এই এন্ট্রিটি জমা দিয়েছিলাম তখন পূর্বরূপটি সঠিক ছিল, তবে পর্যাপ্ত পার্সারের চেয়ে এসও এর কম অনুচ্ছেদে আমার ঘনিষ্ঠ অ্যাঙ্কর ট্যাগটি খেয়েছিল , প্রমাণ করে যে পার্সারগুলি এটিকে ছোট করে দেখানোর মতো কিছু নয় কারণ আপনি যদি রেজিক্সগুলি ব্যবহার করেন এবং একটি হ্যাক আপনাকে হ্যাক করে you সম্ভবত সূক্ষ্ম এবং ছোট কিছু ভুল পেতে হবে

[3] পার্সেক ব্যবহার করে হাস্কেল পার্সারের স্নিপেট: চারটি ফাংশন ক্যালকুলেটর এক্সটেনশনস, বন্ধনী, গুণের জন্য সাদা স্থান এবং ধীরে ধীরে (পাই এবং ই এর মতো) প্রসারিত।

aexpr   =   expr `chainl1` toOp
expr    =   optChainl1 term addop (toScalar 0)
term    =   factor `chainl1` mulop
factor  =   sexpr  `chainr1` powop
sexpr   =   parens aexpr
        <|> scalar
        <|> ident

powop   =   sym "^" >>= return . (B Pow)
        <|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))

toOp    =   sym "->" >>= return . (B To)

mulop   =   sym "*" >>= return . (B Mul)
        <|> sym "/" >>= return . (B Div)
        <|> sym "%" >>= return . (B Mod)
        <|>             return . (B Mul)

addop   =   sym "+" >>= return . (B Add) 
        <|> sym "-" >>= return . (B Sub)

scalar = number >>= return . toScalar

ident  = literal >>= return . Lit

parens p = do
             lparen
             result <- p
             rparen
             return result

9
আমার বক্তব্যকে জোর দেওয়ার জন্য, নোট করুন যে আমার পোস্টের মার্কআপটি সঠিকভাবে পার্স হচ্ছে না (এবং এটি স্ট্যাটিকভাবে দেওয়া মার্কআপের মধ্যে এবং ডাব্লুএমডি পূর্বরূপে যে রেন্ডার হয়েছে তার মধ্যে পরিবর্তিত হয়)। এটি ঠিক করার জন্য বেশ কয়েকটি প্রচেষ্টা করা হয়েছে তবে আমি মনে করি পার্সারটি ভুল। প্রত্যেককে অনুগ্রহ করুন এবং সঠিকভাবে বিশ্লেষণ করুন!
জ্যারেড আপডেটিকে

155

Shunting গজ অ্যালগরিদম এই জন্য ডান হাতিয়ার। উইকিপিডিয়া এটি সম্পর্কে সত্যিই বিভ্রান্তিকর, তবে মূলত অ্যালগরিদম এই জাতীয়ভাবে কাজ করে:

বলুন, আপনি 1 + 2 * 3 + 4. মূল্যায়ন করতে চান স্বজ্ঞাতভাবে, আপনি "জানেন" আপনাকে প্রথমে 2 * 3 করতে হবে, তবে আপনি কীভাবে এই ফলাফল পাবেন? কী উপলব্ধি করা যে আপনি যখন স্ট্রিং স্ক্যান করছি বাঁ দিক থেকে ডানদিকে, আপনি যখন অপারেটর যে একটি অপারেটর মূল্যায়ন হবে নিম্নরূপ এটি একটি কম (বা এর সমান) প্রাধান্য। উদাহরণের প্রসঙ্গে আপনি এখানে যা করতে চান তা এখানে:

  1. দেখুন: 1 + 2, কিছু করবেন না।
  2. এখন 1 + 2 * 3 দেখুন, এখনও কিছু করবেন না।
  3. এখন 1 + 2 * 3 + 4 দেখুন, এখন আপনি জানেন যে 2 * 3 মূল্যায়ন করতে হবে কারণ পরবর্তী অপারেটরের কম অগ্রাধিকার রয়েছে।

আপনি কীভাবে এটি বাস্তবায়ন করবেন?

আপনি দুটি স্ট্যাক রাখতে চান, একটি সংখ্যার জন্য এবং অন্যটি অপারেটরগুলির জন্য। আপনি সর্বদা স্ট্যাকের উপর সংখ্যাগুলি ঠেলাবেন। আপনি স্ট্যাকের শীর্ষে থাকা প্রতিটিটির সাথে একটি নতুন অপারেটরকে তুলনা করুন, যদি স্ট্যাকের শীর্ষে থাকা কোনওটির উপর অগ্রাধিকার থাকে তবে আপনি এটি অপারেটর স্ট্যাকের বাইরে ফেলে দিন, অপারেটরটিকে নম্বর স্ট্যাকের বাইরে ফেলে দিন, অপারেটরটি প্রয়োগ করুন এবং ফলাফলটি ধাক্কা দিন নম্বর স্ট্যাক উপর। এখন আপনি স্ট্যাক অপারেটরের শীর্ষের সাথে তুলনাটি পুনরাবৃত্তি করুন।

উদাহরণে ফিরে আসার সাথে সাথে এটি কাজ করে:

এন = [] ওপস = []

  • পড়ুন 1. এন = [1], অপস = []
  • পড়ুন +। এন = [1], অপস = [+]
  • পড়ুন 2. এন = [1 2], অপস = [+]
  • পড়ুন *। এন = [1 2], অপস = [+ *]
  • পড়ুন 3. এন = [1 2 3], অপ্স = [+ *]
  • পড়ুন +। এন = [1 2 3], অপ্স = [+ *]
    • 3, 2 পপ করুন এবং 2 *3 সম্পাদন করুন এবং ফলাফলটিকে N. N = [1 6], অপস = [+] তে চাপ দিন
    • +সাহসী বাকী রয়েছে, সুতরাং আপনি 1, 6 টিও বন্ধ করতে এবং + সম্পাদন করতে চান। এন = [7], অপ্স = []।
    • অবশেষে অপারেটর স্ট্যাকের উপর [+] টিপুন। এন = [7], অপস = [+]।
  • পড়ুন 4. এন = [7 4]। অপ্স = [+]।
  • আপনার ইনপুট বন্ধ হয়ে গেছে, সুতরাং আপনি এখনই স্ট্যাকগুলি খালি করতে চান। যার উপর আপনি ফলাফল পাবেন 11।

সেখানে, এটি এতটা কঠিন নয়, তাই না? এবং এটি কোনও ব্যাকরণ বা পার্সার জেনারেটরের কাছে কোনও অনুরোধ করে না।


6
আপনার আসলে দুটি স্ট্যাকের দরকার নেই, যতক্ষণ না আপনি উপরে পপিং না করে স্ট্যাকের দ্বিতীয় জিনিসটি দেখতে পাচ্ছেন। পরিবর্তে আপনি একটি একক স্ট্যাক ব্যবহার করতে পারেন যা সংখ্যার এবং অপারেটরদের বিকল্প করে। এটি আসলে একটি এলআর পার্সার জেনারেটর (যেমন বাইসন) যা করে ঠিক তার সাথে মিলে যায়।
ক্রিস ডড 21

2
অ্যালগরিদমের সত্যিই দুর্দান্ত ব্যাখ্যা আমি এখনই প্রয়োগ করেছি। এছাড়াও আপনি এটিকে পোস্টফিক্সে রূপান্তর করছেন না এটিও দুর্দান্ত। প্রথম বন্ধনী জন্য সমর্থন যোগ করা খুব সহজ।
জিওরগি

4
শান্টিং -ইয়ার্ড অ্যালগরিদমের জন্য একটি সরলিকৃত সংস্করণটি এখানে পাওয়া যাবে: andreinc.net/2010/10/05/… (জাভা এবং অজগর বাস্তবায়নের সাথে)
আন্দ্রে সিওবানু

1
এই জন্য ধন্যবাদ, আমি ঠিক কি পরে করছি!
জো সবুজ

সাহসী - বাম সম্পর্কে উল্লেখ করার জন্য অনেক ধন্যবাদ। আমি টার্নারি অপারেটরের সাথে আটকেছি: কীভাবে নেস্টেড "?:" দিয়ে জটিল এক্সপ্রেশনগুলি পার্স করবেন? আমি বুঝতে পারি যে দু'জনেই? ' এবং ':' এর একই অগ্রাধিকার থাকতে হবে। এবং যদি আমরা 'ব্যাখ্যা করি?' ডান হিসাবে - সহযোগী এবং ':' বাম হিসাবে - এই অ্যালগরিদম তাদের সাথে খুব ভালভাবে কাজ করে। এছাড়াও, আমরা 2 জন অপারেটর কেবল তখনই ভেঙে ফেলতে পারি যখন তাদের উভয়টিই রেখে যাবে - সহযোগী tive
ভ্লাদিস্লাভ

25

http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm

বিভিন্ন পদ্ধতির খুব ভাল ব্যাখ্যা:

  • পুনরাবৃত্ত-বংশদ্ভুত স্বীকৃতি
  • শান্টিং ইয়ার্ড অ্যালগরিদম
  • ক্লাসিক সমাধান
  • অগ্রাধিকার আরোহণ

সহজ ভাষায় এবং সিউডো কোডে রচিত।

আমি 'অগ্রাধিকার আরোহণ' এক পছন্দ করি।


লিঙ্কটি নষ্ট হয়ে গেছে বলে মনে হচ্ছে। এর থেকে আরও ভাল উত্তরটি কী হতে পারে তা প্রতিটি পদ্ধতির প্যারাফ্রেস করা হত যাতে সেই লিঙ্কটি যখন অদৃশ্য হয়ে যায়, সেই দরকারী কিছু তথ্য এখানে সংরক্ষণ করা যেত।
অ্যাডাম হোয়াইট

18

অপারেটর-অগ্রাধিকার পার্সিংয়ের সাথে একটি সাধারণ পুনরাবৃত্ত-বংশদ্ভুত পার্সার সংমিশ্রণ সম্পর্কে এখানে একটি নিবন্ধ রয়েছে । আপনি যদি সম্প্রতি পার্সার লিখছেন তবে এটি পড়ার জন্য খুব আকর্ষণীয় এবং শিক্ষামূলক হতে হবে।


16

অনেক দিন আগে, আমি আমার নিজস্ব পার্সিং অ্যালগরিদম তৈরি করেছি, যা পার্সিং সম্পর্কিত কোনও বইতে পাই না (ড্রাগনের বইয়ের মতো)। শান্টিং ইয়ার্ড অ্যালগরিদমের দিকে নির্দেশকগুলির দিকে তাকিয়ে আমি সাদৃশ্যটি দেখতে পাচ্ছি।

প্রায় 2 বছর আগে, আমি http://www.perlmonks.org/?node_id=554516 এ পার্ল উত্স কোড দিয়ে সম্পূর্ণ এটি নিয়ে একটি পোস্ট করেছি made । অন্যান্য ভাষাগুলিতে পোর্ট করা সহজ: আমি প্রথম প্রয়োগটি জেড 80 এসেম্বলারের মধ্যে করেছিলাম।

এটি সংখ্যার সাথে সরাসরি গণনার জন্য আদর্শ, তবে আপনি যদি প্রয়োজন হয় তবে এটি একটি পার্স গাছ তৈরি করতে ব্যবহার করতে পারেন।

হালনাগাদ কারণ আরও লোক জাভাস্ক্রিপ্টটি পড়তে (বা চালাতে পারে), কোডটি পুনর্গঠিত হওয়ার পরে আমি জাভাস্ক্রিপ্টে আমার পার্সারটিকে পুনরায় প্রয়োগ করেছি। পুরো পার্সারটি জাভাস্ক্রিপ্ট কোডের 5k এর অধীনে রয়েছে (পার্সারের প্রায় 100 টি লাইন, একটি মোড়ক ফাংশনের জন্য 15 লাইন) ত্রুটি প্রতিবেদন করা, এবং মন্তব্যগুলি সহ।

আপনি http://users.telenet.be/bartl/expressionParser/expressionParser.html এ একটি লাইভ ডেমো খুঁজে পেতে পারেন ।

// operator table
var ops = {
   '+'  : {op: '+', precedence: 10, assoc: 'L', exec: function(l,r) { return l+r; } },
   '-'  : {op: '-', precedence: 10, assoc: 'L', exec: function(l,r) { return l-r; } },
   '*'  : {op: '*', precedence: 20, assoc: 'L', exec: function(l,r) { return l*r; } },
   '/'  : {op: '/', precedence: 20, assoc: 'L', exec: function(l,r) { return l/r; } },
   '**' : {op: '**', precedence: 30, assoc: 'R', exec: function(l,r) { return Math.pow(l,r); } }
};

// constants or variables
var vars = { e: Math.exp(1), pi: Math.atan2(1,1)*4 };

// input for parsing
// var r = { string: '123.45+33*8', offset: 0 };
// r is passed by reference: any change in r.offset is returned to the caller
// functions return the parsed/calculated value
function parseVal(r) {
    var startOffset = r.offset;
    var value;
    var m;
    // floating point number
    // example of parsing ("lexing") without aid of regular expressions
    value = 0;
    while("0123456789".indexOf(r.string.substr(r.offset, 1)) >= 0 && r.offset < r.string.length) r.offset++;
    if(r.string.substr(r.offset, 1) == ".") {
        r.offset++;
        while("0123456789".indexOf(r.string.substr(r.offset, 1)) >= 0 && r.offset < r.string.length) r.offset++;
    }
    if(r.offset > startOffset) {  // did that work?
        // OK, so I'm lazy...
        return parseFloat(r.string.substr(startOffset, r.offset-startOffset));
    } else if(r.string.substr(r.offset, 1) == "+") {  // unary plus
        r.offset++;
        return parseVal(r);
    } else if(r.string.substr(r.offset, 1) == "-") {  // unary minus
        r.offset++;
        return negate(parseVal(r));
    } else if(r.string.substr(r.offset, 1) == "(") {  // expression in parens
        r.offset++;   // eat "("
        value = parseExpr(r);
        if(r.string.substr(r.offset, 1) == ")") {
            r.offset++;
            return value;
        }
        r.error = "Parsing error: ')' expected";
        throw 'parseError';
    } else if(m = /^[a-z_][a-z0-9_]*/i.exec(r.string.substr(r.offset))) {  // variable/constant name        
        // sorry for the regular expression, but I'm too lazy to manually build a varname lexer
        var name = m[0];  // matched string
        r.offset += name.length;
        if(name in vars) return vars[name];  // I know that thing!
        r.error = "Semantic error: unknown variable '" + name + "'";
        throw 'unknownVar';        
    } else {
        if(r.string.length == r.offset) {
            r.error = 'Parsing error at end of string: value expected';
            throw 'valueMissing';
        } else  {
            r.error = "Parsing error: unrecognized value";
            throw 'valueNotParsed';
        }
    }
}

function negate (value) {
    return -value;
}

function parseOp(r) {
    if(r.string.substr(r.offset,2) == '**') {
        r.offset += 2;
        return ops['**'];
    }
    if("+-*/".indexOf(r.string.substr(r.offset,1)) >= 0)
        return ops[r.string.substr(r.offset++, 1)];
    return null;
}

function parseExpr(r) {
    var stack = [{precedence: 0, assoc: 'L'}];
    var op;
    var value = parseVal(r);  // first value on the left
    for(;;){
        op = parseOp(r) || {precedence: 0, assoc: 'L'}; 
        while(op.precedence < stack[stack.length-1].precedence ||
              (op.precedence == stack[stack.length-1].precedence && op.assoc == 'L')) {  
            // precedence op is too low, calculate with what we've got on the left, first
            var tos = stack.pop();
            if(!tos.exec) return value;  // end  reached
            // do the calculation ("reduce"), producing a new value
            value = tos.exec(tos.value, value);
        }
        // store on stack and continue parsing ("shift")
        stack.push({op: op.op, precedence: op.precedence, assoc: op.assoc, exec: op.exec, value: value});
        value = parseVal(r);  // value on the right
    }
}

function parse (string) {   // wrapper
    var r = {string: string, offset: 0};
    try {
        var value = parseExpr(r);
        if(r.offset < r.string.length){
          r.error = 'Syntax error: junk found at offset ' + r.offset;
            throw 'trailingJunk';
        }
        return value;
    } catch(e) {
        alert(r.error + ' (' + e + '):\n' + r.string.substr(0, r.offset) + '<*>' + r.string.substr(r.offset));
        return;
    }    
}

11

আপনি বর্তমানে যে ব্যাকরণটি পার্স করার জন্য ব্যবহার করছেন তা বর্ণনা করতে পারলে এটি সাহায্য করবে। সমস্যা মনে হচ্ছে সেখানে থাকতে পারে!

সম্পাদনা:

ব্যাকরণের প্রশ্নটি আপনি বুঝতে না পেরেছেন এবং 'আপনি এটি হাতে লিখে লিখেছেন' খুব সম্ভবত ব্যাখ্যা করে যে আপনি কেন '1 + 11 * 5' ফর্মটি প্রকাশ করতে সমস্যা করছেন (অর্থাত্ অপারেটরের অগ্রাধিকার সহ) । 'গাণিতিক এক্সপ্রেশনগুলির জন্য ব্যাকরণের' জন্য গুগলিং, উদাহরণস্বরূপ, কিছু ভাল পয়েন্টার পাওয়া উচিত। এ জাতীয় ব্যাকরণ জটিল হওয়ার দরকার নেই:

<Exp> ::= <Exp> + <Term> |
          <Exp> - <Term> |
          <Term>

<Term> ::= <Term> * <Factor> |
           <Term> / <Factor> |
           <Factor>

<Factor> ::= x | y | ... |
             ( <Exp> ) |
             - <Factor> |
             <Number>

উদাহরণস্বরূপ কৌশলটি ব্যবহার করবে এবং আরও কিছু জটিল অভিব্যক্তি (উদাহরণস্বরূপ, বা ক্ষমতাগুলি সহ ...) যত্ন নেওয়ার জন্য তুচ্ছভাবে বাড়ানো যেতে পারে।

উদাহরণস্বরূপ, এই থ্রেডটিতে আপনার নজর দেওয়া উচিত suggest

ব্যাকরণ / পার্সিংয়ের প্রায় সমস্ত ভূমিকা উদাহরণ হিসাবে পাটিগণিতের অভিব্যক্তিকে বিবেচনা করে।

দ্রষ্টব্য যে ব্যাকরণ ব্যবহার করা মোটেই কোনও নির্দিষ্ট সরঞ্জাম ( একটি লা ইয়্যাক, বাইসন, ...) ব্যবহার করে বোঝায় না । প্রকৃতপক্ষে, আপনি অবশ্যই নিচের ব্যাকরণটি ইতিমধ্যে ব্যবহার করছেন:

<Exp>  :: <Leaf> | <Exp> <Op> <Leaf>

<Op>   :: + | - | * | /

<Leaf> :: <Number> | (<Exp>)

(বা ধরনের কিছু) না জেনে!


8

আপনি বুস্ট স্পিরিট ব্যবহার সম্পর্কে চিন্তাভাবনা করেছেন ? এটি আপনাকে C ++ তে EBNF- এর মতো ব্যাকরণ লিখতে দেয়:

group       = '(' >> expression >> ')';
factor      = integer | group;
term        = factor >> *(('*' >> factor) | ('/' >> factor));
expression  = term >> *(('+' >> term) | ('-' >> term));

1
+1 এবং উত্সাহটি হ'ল, সবকিছু বুস্টের অংশ। ক্যালকুলেটারের ব্যাকরণটি এখানে: স্পিরিটস.সোর্স.সোর্স.কম / ডিস্ট্রিবি / স্পিরিট_1_8_5 / libs / spirit / example/… । ক্যালকুলেটরটির বাস্তবায়নটি এখানে: স্পিরিটস.সোর্সফোর্জন . नेट / ডিস্ট্রিবি / স্পিরিট_1_8_5 / libs / spirit / example/… । এবং ডকুমেন্টেশনটি এখানে: স্পিরিটস.সোর্সফোর্জন . नेट / ডিস্ট্রিবি / স্পরিট_1_8_5 / libs / spirit / doc/… । আমি এখনও বুঝতে পারি না কেন লোকেরা এখনও সেখানে মিনি পার্সারগুলির নিজস্ব প্রয়োগ করে।
স্টেফান

5

আপনি যেমন আপনার প্রশ্নটি রেখেছেন ততবার পুনরাবৃত্তি করার দরকার নেই। উত্তরটি তিনটি বিষয়: পোস্টফিক্স স্বরলিপি প্লাস শান্টিং ইয়ার্ড অ্যালগোরিদম প্লাস পোস্টফিক্স এক্সপ্রেশন মূল্যায়ন:

1)। পোস্টফিক্স স্বরলিপি = সুস্পষ্ট অগ্রাধিকারের নির্দিষ্টকরণের প্রয়োজনীয়তা দূর করার জন্য উদ্ভাবিত। নেটে আরও পড়ুন তবে এখানে এটির সংক্ষেপণটি হল: ইনফিক্স এক্সপ্রেশন (1 + 2) * 3 যদিও মেশিনের মাধ্যমে কম্পিউটিংয়ের জন্য খুব দক্ষ না পড়তে ও পড়তে মানুষের পক্ষে সহজ। কি? সরল নিয়ম যা "অগ্রাধিকারের ক্ষেত্রে ক্যাশে হয়ে ভাবের পুনর্লিখন করে, তারপরে সর্বদা এটি বাম থেকে ডানে প্রক্রিয়া করে" বলে। সুতরাং ইনফিক্স (1 + 2) * 3 একটি পোস্টফিক্স 12 + 3 * হয়। পোস্ট করুন কারণ অপারেটরগুলির পরে অপারেটর সর্বদা স্থাপন করা হয়।

2)। পোস্টফিক্স এক্সপ্রেশন মূল্যায়ন। সহজ। পোস্টফিক্স স্ট্রিংয়ের বাইরে সংখ্যা পড়ুন। অপারেটর দেখা না পাওয়া পর্যন্ত এগুলিকে একটি স্ট্যাকে ঠেলাও। অপারেটরের ধরণটি পরীক্ষা করুন - একা? বাইনারি? তৃতীয় পর্যায়ের? এই অপারেটরকে মূল্যায়নের জন্য প্রয়োজনীয় যতগুলি অপারেশন স্ট্যাক বন্ধ করুন। মূল্যনির্ধারণ করা। স্ট্যাকের উপর আবার ফলাফল ঠেলা! এবং আপনি প্রায় সম্পন্ন। স্ট্যাকের কেবল একটি প্রবেশিকা = মান ইউর না হওয়া পর্যন্ত এটি চালিয়ে যান।

আসুন (1 + 2) * 3 যা পোস্টফিক্সে রয়েছে "12 + 3 *"। প্রথম সংখ্যাটি পড়ুন = 1. স্ট্যাকের উপর চাপ দিন। পরবর্তী পড়ুন। সংখ্যা = 2. এটি স্ট্যাকের উপর চাপুন। পরবর্তী পড়ুন। অপারেটর. কোনটি? + +। কি ধরনের? বাইনারি = দুটি অপারেন্ডের প্রয়োজন। পপ স্ট্যাক দুইবার = যথাক্রমে 2 এবং আরগলফ্যাটটি 1 টি 1 + 2 হয় 3 স্ট্যাকের উপরে 3 টিপুন। পোস্টফিক্স স্ট্রিং থেকে পরবর্তী পড়ুন। এটি একটি সংখ্যা। 3.Push। পরবর্তী পড়ুন। অপারেটর. কোনটি? *। কি ধরনের? বাইনারি = দুটি সংখ্যা প্রয়োজন -> পপ স্ট্যাক দুইবার ack প্রথম পপটি অর্গ্রেটে, দ্বিতীয়বার আর্গলেটে। অপারেশন মূল্যায়ন - 3 বার 3 হয় 9. স্ট্যাক 9 ধাক্কা। পরবর্তী পোস্টফিক্স চর পড়ুন। এটা নাল। ইনপুট সমাপ্তি। পপ স্ট্যাক অনেক = এটি আপনার উত্তর।

3)। শান্টিং ইয়ার্ডটি মানুষের (সহজেই) পঠনযোগ্য ইনফিক্স এক্সপ্রেশনকে পোস্টফিক্স এক্সপ্রেশনে রূপান্তর করতে ব্যবহৃত হয় (কিছু অনুশীলনের পরেও মানুষ সহজেই পঠনযোগ্য)। ম্যানুয়ালি কোড করা সহজ। উপরের মন্তব্য এবং নেট দেখুন।


4

আপনি কোন ভাষা ব্যবহার করতে চান? এএনটিএলআর আপনাকে জাভা দৃষ্টিকোণ থেকে এটি করতে দেয়। অ্যাড্রিয়ান কুহানের রুবিতে কীভাবে এক্সিকিউটেবল ব্যাকরণ রচনা করা যায় সে সম্পর্কে একটি দুর্দান্ত রচনাআপ রয়েছে; আসলে, তার উদাহরণটি হুবহু আপনার গাণিতিক প্রকাশের উদাহরণ।


আমাকে অবশ্যই স্বীকার করতে হবে যে আমার ব্লগ পোস্টে দেওয়া উদাহরণগুলি বাম-পুনরাবৃত্তি ভুল হয়ে যাচ্ছে, অর্থাত্ a - b - c ((a -b) - c) এর পরিবর্তে (a - (b -c)) এ মূল্যায়ন করে। আসলে, এটি আমার একটি টোড যুক্ত করার স্মরণ করিয়ে দেয় যে আমার ব্লগ পোস্টগুলি ঠিক করা উচিত।
akuhn

4

এটি আপনি কীভাবে "সাধারণ" হতে চান তা নির্ভর করে।

আপনি যদি এটি সত্যিই সাধারণ হতে চান যেমন গাণিতিক ক্রিয়াকলাপ পাশাপাশি পার্ট (4 + 5) * কোস (7 ^ 3) হিসাবে পার্স করতে সক্ষম হন তবে আপনার সম্ভবত একটি পার্স গাছ লাগবে

যার মধ্যে, আমি মনে করি না যে এখানে একটি সম্পূর্ণ বাস্তবায়ন সঠিকভাবে আটকানো উপযুক্ত। আমি আপনাকে পরামর্শ দিচ্ছি যে আপনি একটি কুখ্যাত " ড্রাগন বই " পরীক্ষা করে দেখুন " পরীক্ষা করে দেখুন।

তবে আপনি যদি কেবল অগ্রাধিকার সমর্থন চান , তবে আপনি প্রথমে অভিব্যক্তিটিকে পোস্টফিক্স ফর্মে রূপান্তর করে এমনটি করতে পারেন যার অনুলিপিটি আপনি অনুলিপি-পেস্ট করতে পারেন তা গুগল থেকে পাওয়া উচিত বা আমার মনে হয় আপনি বাইনারি দিয়ে নিজেকে কোড আপ করতে পারেন গাছ।

আপনার যখন এটি পোস্টফিক্স আকারে রয়েছে, তারপরে এটি একেবারে কেকের টুকরো যেহেতু আপনি ইতিমধ্যে বুঝতে পারছেন যে স্ট্যাকটি কীভাবে সহায়তা করে।


ড্রাগন বইটি এক্সপ্রেশন মূল্যায়নকারীর জন্য কিছুটা অতিরিক্ত হতে পারে - একটি সাধারণ পুনরাবৃত্তি বংশদ্ভুত পার্সার যা প্রয়োজন তা সমস্তই, তবে আপনি যদি সংকলকগুলিতে আরও বিস্তৃত কিছু করতে চান তবে এটি অবশ্যই পড়তে হবে।
অন্ধকার

1
বাহ - এটি জানতে পেরে খুব সুন্দর যে "ড্রাগন বই" এখনও আলোচনা করা হয়েছে। আমি মনে করি 30 বছর আগে বিশ্ববিদ্যালয়ে এটি পড়াশোনা করা - এবং সমস্তটি পড়া।
শ্রয়েডিংগার্স বিড়াল

4

আমি প্রতারণা এবং শান্টিং ইয়ার্ড অ্যালগরিদম ব্যবহার করার পরামর্শ দেব । এটি একটি সাধারণ ক্যালকুলেটর-জাতীয় পার্সার লেখার একটি সহজ মাধ্যম এবং বিবেচনায় অগ্রাধিকার গ্রহণ করে।

আপনি যদি জিনিসগুলিতে সঠিকভাবে টোকেনাইজ করতে চান এবং ভেরিয়েবল ইত্যাদি জড়িত থাকতে চান তবে আমি এখানে গিয়ে অন্যদের পরামর্শ অনুসারে একটি পুনরাবৃত্ত বংশদ্ভুত পার্সার লিখব, তবে আপনার যদি কেবল একটি ক্যালকুলেটর-স্টাইলের পার্সার প্রয়োজন হয় তবে এই অ্যালগরিদম যথেষ্ট পরিমাণে হওয়া উচিত :-)


4

শান্টিং ইয়ার্ড অ্যালগরিদম সম্পর্কে আমি পিকলিস্টে এটি পেয়েছি :

হ্যারল্ড লিখেছেন:

আমার মনে আছে পড়াশোনা, অনেক আগে, একটি অ্যালগরিদম যা সহজে মূল্যায়নের জন্য বীজগণিত প্রকাশগুলি আরপিএন-তে রূপান্তরিত করে। প্রতিটি ইনফিক্স মান বা অপারেটর বা প্রথম বন্ধনী ট্র্যাকের একটি রেলপথ গাড়ি দ্বারা প্রতিনিধিত্ব করা হয়েছিল। এক ধরণের গাড়ি অন্য ট্রাকে বিভক্ত হয়ে যায় এবং অন্যটি সরাসরি এগিয়ে চলেছিল continued আমি বিবরণগুলি (স্পষ্টতই!) মনে করি না, তবে সর্বদা ভেবেছিলাম কোডটি আকর্ষণীয় হবে। আমি ফিরে এসেছি যখন আমি 6800 (68000 নয়) সমাবেশ কোড লিখছিলাম writing

এটি "শান্টিং ইয়ার্ড অ্যালগরিদম" এবং বেশিরভাগ মেশিন পার্সার এটি ব্যবহার করে use উইকিপিডিয়ায় পার্সিং সম্পর্কিত নিবন্ধটি দেখুন। শান্টিং ইয়ার্ড অ্যালগরিদম কোড করার একটি সহজ উপায় হ'ল দুটি স্ট্যাক ব্যবহার করা। একটি হ'ল "পুশ" স্ট্যাক এবং অন্যটি "হ্রাস" বা "ফলাফল" স্ট্যাক। উদাহরণ:

pstack = () // খালি স্টারট্যাক = () ইনপুট: 1 + 2 * 3 অগ্রাধিকার = 10 // সর্বনিম্ন হ্রাস = 0 // হ্রাস করবেন না

শুরু: টোকেন '1': ইসনম্বার, pstack এ রাখা (ধাক্কা) টোকেন '+': আইসোপরেটর সেট অগ্রাধিকার = 2 যদি অগ্রাধিকার <পূর্ববর্তী_পরিষ্কার_পরিবর্তনতা হ্রাস করুন () // নীচে দেখুন '+' pstack (পুশ) টোকেন '2' : ইসনম্বার, প্রেস্ট্যাক (পুশ) টোকেন '*' এ রেখেছেন: আইসোপরেটর, সেট অগ্রাধিকার = 1, প্রেস্টকে (ধাক্কা) // // অগ্রাধিকার হিসাবে // টোকেন '3' এর উপরে চেক করুন: ইসনম্বার, পিস্ট্যাকের (পুশ) প্রান্তে রেখে দিন ইনপুট, হ্রাস করতে হবে (লক্ষ্য খালি pstack হয়) হ্রাস () // সম্পন্ন হয়েছে

কমাতে, পুশ স্ট্যাক থেকে উপাদানগুলি পপ করুন এবং ফলাফল স্ট্যাকের মধ্যে রাখুন, সর্বদা শীর্ষ 2 আইটেমগুলি স্ট্র্যাপ 'অপারেটর' 'নম্বর' এর থেকে থাকে তবে সর্বদা পস্টকে স্ট্যাপ করুন:

pstack: '1' '+' '' '' '' 3 'স্টার স্ট্যাক: () ... pstack: () স্টার স্ট্যাক:' 3 '' 2 '' ' '1' '+'

অভিব্যক্তিটি যদি হত:

1 * 2 +3

তাহলে হ্রাস ট্রিগারটি হ'ল টোকেন '+' পড়তে হবে যা ইতিমধ্যে "*" ধাক্কা খাওয়ার চেয়ে কম রয়েছে, তাই এটি করা হত:

pstack: '1' ' ' '2' স্টার স্ট্যাক: () ... pstack: () স্টার স্ট্যাক: '1' '2' ' '

এবং তারপরে '+' এবং তারপরে '3' এবং তারপরে অবশেষে হ্রাস করা হয়েছে:

pstack: '+' '3' স্টার্ট: '1' '2' ' ' ... ... pstack: () স্টারস্টাক: '1' '2' ' ' '3' '+'

সুতরাং সংক্ষিপ্ত সংস্করণটি হ'ল: পুশ সংখ্যাগুলি, যখন অপারেটরগুলি পুশ করে পূর্ববর্তী অপারেটরের অগ্রাধিকার পরীক্ষা করে। যদি এটি অপারেটরের চেয়ে বেশি হয় তবে এটি এখন ধাক্কা দিতে হবে, প্রথমে হ্রাস করুন, তারপরে বর্তমান অপারেটরটিকে ধাক্কা দিন। পেরেনগুলি হ্যান্ডেল করতে কেবল 'পূর্ববর্তী' অপারেটরের নজরে সংরক্ষণ করুন এবং স্ট্রোকের উপর একটি চিহ্ন রাখুন যা প্যারেন জোড়ার অভ্যন্তরের সমাধানের সময় হ্রাস করা বন্ধ করতে অ্যালগরিদমকে বলে। বন্ধ হওয়া পেরেন ইনপুটটির শেষের মতোই হ্রাসকে ট্রিগার করে এবং pstack থেকে খোলা পেরেন চিহ্নটিও সরিয়ে দেয় এবং 'পূর্ববর্তী ক্রিয়াকলাপ'-এর পুনরুদ্ধারটিকে পুনরুদ্ধার করে যাতে পার্সিং বন্ধ হয়ে যাওয়া বন্ধের পরেও চালিয়ে যেতে পারে where এটি পুনরাবৃত্তির সাথে বা ছাড়াই করা যেতে পারে (ইঙ্গিত: '(' ...) এর মুখোমুখি হওয়ার সময় পূর্ববর্তী নজরে রাখতে স্ট্যাক ব্যবহার করুন ack এর সাধারণ সংস্করণ হ'ল শান্টিং ইয়ার্ড অ্যালগরিদম, এফ.এক্স. প্রয়োগ করা পার্সার জেনারেটর ব্যবহার করা to ইয়্যাক বা বাইসন বা ট্যাকল (ইয়্যাকের টিসিএল এনালগ) ব্যবহার করে।

পিটার

-Adam


4

অগ্রাধিকার পার্সিংয়ের আরেকটি সংস্থান হ'ল উইকিপিডিয়ায় অপারেটর-অগ্রাধিকার পার্সার এন্ট্রি। ডিজকস্ট্রার শান্টিং ইয়ার্ড অ্যালগরিদম এবং একটি বৃক্ষ বিকল্প অ্যালগরিদমকে কভার করে তবে আরও উল্লেখযোগ্যভাবে একটি সাধারণ সরল ম্যাক্রো রিপ্লেসমেন্ট অ্যালগরিদমকে আচ্ছাদিত করে যা তুচ্ছভাবে কোনও অগ্রাধিকারের অজানা পার্সারের সামনে প্রয়োগ করা যেতে পারে:

#include <stdio.h>
int main(int argc, char *argv[]){
  printf("((((");
  for(int i=1;i!=argc;i++){
    if(argv[i] && !argv[i][1]){
      switch(argv[i]){
      case '^': printf(")^("); continue;
      case '*': printf("))*(("); continue;
      case '/': printf("))/(("); continue;
      case '+': printf(")))+((("); continue;
      case '-': printf(")))-((("); continue;
      }
    }
    printf("%s", argv[i]);
  }
  printf("))))\n");
  return 0;
}

এটি হিসাবে চালান:

$ cc -o parenthesise parenthesise.c
$ ./parenthesise a \* b + c ^ d / e
((((a))*((b)))+(((c)^(d))/((e))))

যা এর সরলতায় দুর্দান্ত এবং খুব বোধগম্য।


3
এটি বেশ সুন্দর ছোট মুক্তো। তবে এটি প্রসারিত করুন (বলুন, ফাংশন অ্যাপ্লিকেশন সহ, অন্তর্নির্মিত গুণ, উপসর্গ এবং পোস্টফিক্স অপারেটরগুলি, typeচ্ছিক ধরণের টীকাগুলি, যে কোনও কিছু) পুরো জিনিসটি ভেঙে দেয়। অন্য কথায়, এটি একটি মার্জিত হ্যাক।
জারেড আপডেটিকে 18

আমি বিন্দু দেখতে পাচ্ছি না। এই সমস্তটি হ'ল অপারেটর-অগ্রাধিকার পার্সিংয়ের সমস্যাটিকে প্যারেন্টেসিস-অগ্রাধিকার পার্সিং সমস্যায় পরিবর্তন করা।
লার্নের মারকুইস

@ ইজেপি নিশ্চিত, তবে প্রশ্নের পার্সার প্যারেন্সেসিসটি ঠিকঠাক পরিচালনা করে, সুতরাং এটি একটি যুক্তিসঙ্গত সমাধান। আপনার যদি পার্সার থাকে তবে তা না করে তবে আপনি সঠিক আছেন যে এটি সমস্যাটিকে অন্য কোনও অঞ্চলে নিয়ে যায়।
অ্যাডাম ডেভিস

4

আমি আমার ওয়েবসাইটে একটি আল্ট্রা কমপ্যাক্টের জন্য উত্স পোস্ট করেছি (1 শ্রেণি, <10 কিবি) জাভা ম্যাথ মূল্যায়নকারী আমার ওয়েবসাইটে। এটি গ্রহণযোগ্য উত্তরের পোস্টারের জন্য ক্র্যানিয়াল বিস্ফোরণ ঘটায় এমন ধরণের একটি পুনরাবৃত্তিক বংশদ্ভুত পার্সার।

এটি সম্পূর্ণ অগ্রাধিকার, প্রথম বন্ধনী, নামযুক্ত ভেরিয়েবল এবং একক যুক্তি ফাংশন সমর্থন করে।




2

আমি বর্তমানে ডিজাইনের নিদর্শন এবং পঠনযোগ্য প্রোগ্রামিংয়ের জন্য একটি শেখার সরঞ্জাম হিসাবে নিয়মিত এক্সপ্রেশন পার্সার তৈরির ধারাবাহিক নিবন্ধগুলিতে কাজ করছি। আপনি পঠনযোগ্য কোডে একবার দেখে নিতে পারেন । নিবন্ধটি শান্টিং ইয়ার্ডের অ্যালগরিদমের একটি পরিষ্কার ব্যবহার উপস্থাপন করেছে।


2

আমি এফ # তে একটি এক্সপ্রেশন পার্সার লিখেছি এবং এটি সম্পর্কে এখানে ব্লগ করেছি । এটি শান্টিং ইয়ার্ড অ্যালগরিদম ব্যবহার করে তবে ইনফিক্স থেকে আরপিএন রূপান্তর করার পরিবর্তে, আমি গণনার ফলাফলগুলি সংগ্রহ করতে দ্বিতীয় স্ট্যাক যুক্ত করেছি। এটি সঠিকভাবে অপারেটর অগ্রাধিকার পরিচালনা করে, তবে অ্যানারি অপারেটরদের সমর্থন করে না। যদিও আমি এফ # শিখতে এটি লিখলাম, যদিও এক্সপ্রেশন পার্সিং শিখি না।


2

পাইপার্সিং ব্যবহার করে পাইথন সলিউশনটি এখানে পাওয়া যাবে । অগ্রাধিকার সহ বিভিন্ন অপারেটরের সাথে ইনফিক্স সংকেত পার্স করা মোটামুটি সাধারণ, এবং তাই পাইপার্সিংয়ের সাথে infixNotation(পূর্বে operatorPrecedence) এক্সপ্রেশন বিল্ডারও অন্তর্ভুক্ত রয়েছে । এটির সাহায্যে আপনি উদাহরণস্বরূপ "AND", "OR", "না" ব্যবহার করে বুলিয়ান এক্সপ্রেশনগুলি সহজেই সংজ্ঞায়িত করতে পারেন। অথবা আপনি অন্য চারটি অপারেটর ব্যবহার করতে যেমন আপনার ফোর-ফাংশন গাণিতিকটি প্রসারিত করতে পারেন! ফ্যাক্টরিয়াল, বা মডুলাসের জন্য '%', বা পি এবং সি অপারেটরগুলিকে ক্রমাগতকরণ এবং সংমিশ্রণগুলি গণনা করতে যুক্ত করুন। আপনি ম্যাট্রিক্স নোটেশনের জন্য একটি ইনফিক্স পার্সার লিখতে পারেন, এতে '-1' বা 'টি' অপারেটরদের (বিপরীতকরণ এবং ট্রান্সপোজোর জন্য) পরিচালনা করা অন্তর্ভুক্ত রয়েছে। অপারেটর 4-ফাংশন পার্সারের উদাহরণস্বরূপ ('সহ!'


1

আমি জানি এটি একটি দেরী উত্তর, তবে আমি সবেমাত্র একটি ক্ষুদ্র পার্সার লিখেছি যা সমস্ত অপারেটরকে (উপসর্গ, পোস্টফিক্স এবং ইনফিক্স-বাম, ইনফিক্স-ডান এবং নানাসোসিয়েটিভ) স্বেচ্ছাচারিত প্রাধান্য পেতে দেয়।

আমি এটি নির্বিচারে ডিএসএল সহায়তার সাথে একটি ভাষার জন্য প্রসারিত করতে যাচ্ছি, তবে আমি কেবল এটি উল্লেখ করতে চেয়েছিলাম যে অপারেটর অগ্রাধিকারের জন্য কাস্টম পার্সার প্রয়োজন নেই, কেউ সাধারণীকরণের পার্সার ব্যবহার করতে পারেন যা টেবিলের প্রয়োজন নেই, এবং প্রতিটি অপারেটরের উপস্থিতি হিসাবে এটি কেবল নজরে আসে। লোকেরা কাস্টম প্র্যাট পার্সার বা শান্টিং ইয়ার্ড পার্সারগুলি উল্লেখ করেছে যা অবৈধ ইনপুট গ্রহণ করতে পারে - এটির জন্য কাস্টমাইজ করার দরকার নেই এবং (কোনও সমস্যা না থাকলে) খারাপ ইনপুট গ্রহণ করবে না। এটি একটি অর্থে সম্পূর্ণ নয়, এটি অ্যালগরিদম পরীক্ষা করার জন্য লেখা হয়েছিল এবং এর ইনপুটটি এমন একটি ফর্মে রয়েছে যাতে কিছু প্রাকপ্রসেসিং প্রয়োজন হবে, তবে এমন মন্তব্য রয়েছে যা এটি পরিষ্কার করে দেয়।

নোট করুন কিছু সাধারণ অপারেটর যেমন উদাহরণস্বরূপ যেমন টেবিল [সূচী] বা কোনও ফাংশন ফাংশন (প্যারামিটার-এক্সপ্রেশন, ...) কল করার জন্য ব্যবহৃত অপারেটর অনুপস্থিত রয়েছে আমি সেগুলি যুক্ত করতে যাচ্ছি তবে উভয়কেই পোস্টফিক্স হিসাবে ভাবেন অপারেটরগুলি যেখানে ডিলিমিটারগুলির মধ্যে ['' এবং ']' বা '(' এবং ')' এর মধ্যে আসে তা এক্সপ্রেশন পার্সারের আলাদা উদাহরণ দিয়ে বিশ্লেষণ করে। এটি ছেড়ে দেওয়ার জন্য দুঃখিত, তবে পোস্টফিক্স অংশটি রয়েছে - বাকিগুলি যুক্ত করা কোডের আকারের প্রায় দ্বিগুণ হয়ে যাবে।

যেহেতু পার্সারটি র‌্যাকেট কোডের মাত্র 100 লাইন, সম্ভবত আমার কেবল এটি এখানে আটকানো উচিত, আমি আশা করি এটি স্ট্যাকওভারফ্লো অনুমোদিত হওয়ার চেয়ে দীর্ঘ নয়।

স্বেচ্ছাসেবী সিদ্ধান্ত সম্পর্কে কয়েকটি বিশদ:

যদি একটি নিম্ন অগ্রাধিকার পোস্টফিক্স অপারেটর একটি নিম্ন অগ্রাধিকার প্রিফিক্স অপারেটর হিসাবে একই ইনফিক্স ব্লকের জন্য প্রতিযোগিতা করে থাকে তবে উপসর্গ অপারেটর জিতবে। এটি বেশিরভাগ ভাষায় প্রকাশিত হয় না কারণ বেশিরভাগেরই কম অগ্রাধিকার পোস্টফিক্স অপারেটর নেই। - উদাহরণস্বরূপ: ((ডেটা এ) (বাম 1 +) (প্রাক 2 নন) (ডেটা বি) (পোস্ট 3!) (বাম 1 +) (ডেটা সি)) যেখানে একটি নয় + বি! + সি উপসর্গ অপারেটর এবং! পোস্টফিক্স অপারেটর এবং উভয়ের উভয়ই + এর চেয়ে কম প্রাধান্য পায় তাই তারা (এ + বি নয়!) + সি বা একটি হিসাবে (যেমন খ! + সি) অসম্পূর্ণ উপায়ে সর্বদা জিতে থাকে তাই গ্রুপ হিসাবে দেখতে চান, তাই দ্বিতীয়টি এটি পার্স করার উপায়

ননাসোসিয়েটিভ ইনফিক্স অপারেটরগুলি সত্যই সেখানে রয়েছে যাতে আপনার যে অপারেটরগুলি বিভিন্ন ধরণের প্রত্যাবর্তন করে যে তারা একসাথে বোঝার চেয়ে বেশি নয়, তবে এটির জন্য একটি ক্লোডেজের জন্য আলাদা আলাদা এক্সপ্রেশন রয়েছে without যেমন, এই অ্যালগরিদমে, অ-অ্যাসোসিয়েটিভ অপারেটররা কেবল নিজের সাথে নয়, একই নজিরযুক্ত কোনও অপারেটরের সাথে সংযুক্ত হতে অস্বীকার করে। এটি একটি সাধারণ ক্ষেত্রে যেমন <<===> = ইত্যাদি বেশিরভাগ ভাষায় একে অপরের সাথে সংযুক্ত না হন।

বিভিন্ন ধরণের অপারেটর (বাম, উপসর্গ ইত্যাদি) কীভাবে অগ্রাধিকারের সাথে সম্পর্ক ছিন্ন করে সে সম্পর্কে প্রশ্নটি এমন একটি যা সামনে আসা উচিত নয়, কারণ এটি বিভিন্ন ধরণের অপারেটরদের একই নজির দেওয়ার পক্ষে আসলেই অর্থবোধ করে না। এই অ্যালগরিদম সেই ক্ষেত্রে কিছু করে, তবে আমি ঠিক কী তা খুঁজে বের করতেও বিরক্ত করছি না কারণ এই ধরনের ব্যাকরণটি প্রথমে একটি খারাপ ধারণা।

#lang racket
;cool the algorithm fits in 100 lines!
(define MIN-PREC -10000)
;format (pre prec name) (left prec name) (right prec name) (nonassoc prec name) (post prec name) (data name) (grouped exp)
;for example "not a*-7+5 < b*b or c >= 4"
;which groups as: not ((((a*(-7))+5) < (b*b)) or (c >= 4))"
;is represented as '((pre 0 not)(data a)(left 4 *)(pre 5 -)(data 7)(left 3 +)(data 5)(nonassoc 2 <)(data b)(left 4 *)(data b)(right 1 or)(data c)(nonassoc 2 >=)(data 4)) 
;higher numbers are higher precedence
;"(a+b)*c" is represented as ((grouped (data a)(left 3 +)(data b))(left 4 *)(data c))

(struct prec-parse ([data-stack #:mutable #:auto]
                    [op-stack #:mutable #:auto])
  #:auto-value '())

(define (pop-data stacks)
  (let [(data (car (prec-parse-data-stack stacks)))]
    (set-prec-parse-data-stack! stacks (cdr (prec-parse-data-stack stacks)))
    data))

(define (pop-op stacks)
  (let [(op (car (prec-parse-op-stack stacks)))]
    (set-prec-parse-op-stack! stacks (cdr (prec-parse-op-stack stacks)))
    op))

(define (push-data! stacks data)
    (set-prec-parse-data-stack! stacks (cons data (prec-parse-data-stack stacks))))

(define (push-op! stacks op)
    (set-prec-parse-op-stack! stacks (cons op (prec-parse-op-stack stacks))))

(define (process-prec min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((>= (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-prec min-prec stacks))))))))

(define (process-nonassoc min-prec stacks)
  (let [(op-stack (prec-parse-op-stack stacks))]
    (cond ((not (null? op-stack))
           (let [(op (car op-stack))]
             (cond ((> (cadr op) min-prec) 
                    (apply-op op stacks)
                    (set-prec-parse-op-stack! stacks (cdr op-stack))
                    (process-nonassoc min-prec stacks))
                   ((= (cadr op) min-prec) (error "multiply applied non-associative operator"))
                   ))))))

(define (apply-op op stacks)
  (let [(op-type (car op))]
    (cond ((eq? op-type 'post)
           (push-data! stacks `(,op ,(pop-data stacks) )))
          (else ;assume infix
           (let [(tos (pop-data stacks))]
             (push-data! stacks `(,op ,(pop-data stacks) ,tos))))))) 

(define (finish input min-prec stacks)
  (process-prec min-prec stacks)
  input
  )

(define (post input min-prec stacks)
  (if (null? input) (finish input min-prec stacks)
      (let* [(cur (car input))
             (input-type (car cur))]
        (cond ((eq? input-type 'post)
               (cond ((< (cadr cur) min-prec)
                      (finish input min-prec stacks))
                     (else 
                      (process-prec (cadr cur)stacks)
                      (push-data! stacks (cons cur (list (pop-data stacks))))
                      (post (cdr input) min-prec stacks))))
              (else (let [(handle-infix (lambda (proc-fn inc)
                                          (cond ((< (cadr cur) min-prec)
                                                 (finish input min-prec stacks))
                                                (else 
                                                 (proc-fn (+ inc (cadr cur)) stacks)
                                                 (push-op! stacks cur)
                                                 (start (cdr input) min-prec stacks)))))]
                      (cond ((eq? input-type 'left) (handle-infix process-prec 0))
                            ((eq? input-type 'right) (handle-infix process-prec 1))
                            ((eq? input-type 'nonassoc) (handle-infix process-nonassoc 0))
                            (else error "post op, infix op or end of expression expected here"))))))))

;alters the stacks and returns the input
(define (start input min-prec stacks)
  (if (null? input) (error "expression expected")
      (let* [(cur (car input))
             (input-type (car cur))]
        (set! input (cdr input))
        ;pre could clearly work with new stacks, but could it reuse the current one?
        (cond ((eq? input-type 'pre)
               (let [(new-stack (prec-parse))]
                 (set! input (start input (cadr cur) new-stack))
                 (push-data! stacks 
                             (cons cur (list (pop-data new-stack))))
                 ;we might want to assert here that the cdr of the new stack is null
                 (post input min-prec stacks)))
              ((eq? input-type 'data)
               (push-data! stacks cur)
               (post input min-prec stacks))
              ((eq? input-type 'grouped)
               (let [(new-stack (prec-parse))]
                 (start (cdr cur) MIN-PREC new-stack)
                 (push-data! stacks (pop-data new-stack)))
               ;we might want to assert here that the cdr of the new stack is null
               (post input min-prec stacks))
              (else (error "bad input"))))))

(define (op-parse input)
  (let [(stacks (prec-parse))]
    (start input MIN-PREC stacks)
    (pop-data stacks)))

(define (main)
  (op-parse (read)))

(main)

1

এখানে জাভাতে একটি সহজ কেস রিকারসিভ সমাধান লিখিত আছে। মনে রাখবেন এটি নেতিবাচক সংখ্যাগুলি পরিচালনা করে না তবে আপনি এটি করতে চাইলে এটি যোগ করতে পারেন:

public class ExpressionParser {

public double eval(String exp){
    int bracketCounter = 0;
    int operatorIndex = -1;

    for(int i=0; i<exp.length(); i++){
        char c = exp.charAt(i);
        if(c == '(') bracketCounter++;
        else if(c == ')') bracketCounter--;
        else if((c == '+' || c == '-') && bracketCounter == 0){
            operatorIndex = i;
            break;
        }
        else if((c == '*' || c == '/') && bracketCounter == 0 && operatorIndex < 0){
            operatorIndex = i;
        }
    }
    if(operatorIndex < 0){
        exp = exp.trim();
        if(exp.charAt(0) == '(' && exp.charAt(exp.length()-1) == ')')
            return eval(exp.substring(1, exp.length()-1));
        else
            return Double.parseDouble(exp);
    }
    else{
        switch(exp.charAt(operatorIndex)){
            case '+':
                return eval(exp.substring(0, operatorIndex)) + eval(exp.substring(operatorIndex+1));
            case '-':
                return eval(exp.substring(0, operatorIndex)) - eval(exp.substring(operatorIndex+1));
            case '*':
                return eval(exp.substring(0, operatorIndex)) * eval(exp.substring(operatorIndex+1));
            case '/':
                return eval(exp.substring(0, operatorIndex)) / eval(exp.substring(operatorIndex+1));
        }
    }
    return 0;
}

}


1

অ্যালগরিদম সহজেই পুনরাবৃত্তির বংশদ্ভুত পার্সার হিসাবে সি তে এনকোড করা যায়।

#include <stdio.h>
#include <ctype.h>

/*
 *  expression -> sum
 *  sum -> product | product "+" sum
 *  product -> term | term "*" product
 *  term -> number | expression
 *  number -> [0..9]+
 */

typedef struct {
    int value;
    const char* context;
} expression_t;

expression_t expression(int value, const char* context) {
    return (expression_t) { value, context };
}

/* begin: parsers */

expression_t eval_expression(const char* symbols);

expression_t eval_number(const char* symbols) {
    // number -> [0..9]+
    double number = 0;        
    while (isdigit(*symbols)) {
        number = 10 * number + (*symbols - '0');
        symbols++;
    }
    return expression(number, symbols);
}

expression_t eval_term(const char* symbols) {
    // term -> number | expression
    expression_t number = eval_number(symbols);
    return number.context != symbols ? number : eval_expression(symbols);
}

expression_t eval_product(const char* symbols) {
    // product -> term | term "*" product
    expression_t term = eval_term(symbols);
    if (*term.context != '*')
        return term;

    expression_t product = eval_product(term.context + 1);
    return expression(term.value * product.value, product.context);
}

expression_t eval_sum(const char* symbols) {
    // sum -> product | product "+" sum
    expression_t product = eval_product(symbols);
    if (*product.context != '+')
        return product;

    expression_t sum = eval_sum(product.context + 1);
    return expression(product.value + sum.value, sum.context);
}

expression_t eval_expression(const char* symbols) {
    // expression -> sum
    return eval_sum(symbols);
}

/* end: parsers */

int main() {
    const char* expression = "1+11*5";
    printf("eval(\"%s\") == %d\n", expression, eval_expression(expression).value);

    return 0;
}

পরবর্তী লিবস কার্যকর হতে পারে: ইউপানা - কঠোরভাবে গাণিতিক ক্রিয়াকলাপ; tinyexpr - গাণিতিক ক্রিয়াকলাপ + সি গণিত ফাংশন + ব্যবহারকারীর দ্বারা সরবরাহিত একটি; এমপিসি - পার্সার সংযুক্তকারীগুলি

ব্যাখ্যা

আসুন প্রতীকগুলির ক্রম ক্যাপচার করি যা বীজগণিতীয় প্রকাশকে উপস্থাপন করে। প্রথমটি একটি সংখ্যা, এটি দশমিক অঙ্ক যা এক বা একাধিকবার পুনরাবৃত্তি হয়। আমরা উত্পাদন বিধি হিসাবে এই স্বরলিপি উল্লেখ করব।

number -> [0..9]+

এর অপারেন্ডগুলির সাথে সংযোজন অপারেটর অন্য একটি নিয়ম। এটি হয় numberবা কোনও চিহ্ন যা sum "*" sumক্রম প্রতিনিধিত্ব করে।

sum -> number | sum "+" sum

চেষ্টা বিকল্প numberমধ্যে sum "+" sumযে হতে হবে number "+" numberপালাক্রমে মধ্যে প্রসারিত করা যেতে পারে যা [0..9]+ "+" [0..9]+পরিশেষে যে কমে যেতে পারে 1+8কোনটি সঠিক উপরন্তু অভিব্যক্তি।

অন্যান্য বিকল্পগুলিও সঠিক অভিব্যক্তি তৈরি করবে: sum "+" sum-> number "+" sum-> number "+" sum "+" sum-> number "+" sum "+" number-> number "+" number "+" number->12+3+5

কিছুটা হলেও আমরা সম্ভাব্য বীজগণিতীয় অভিব্যক্তি প্রকাশ করে এমন উত্পাদনের নিয়মগুলি ওরফে ব্যাকরণের সাথে সাদৃশ্য রাখতে পারি ।

expression -> sum
sum -> difference | difference "+" sum
difference -> product | difference "-" product
product -> fraction | fraction "*" product
fraction -> term | fraction "/" term
term -> "(" expression ")" | number
number -> digit+                                                                    

অপারেটর অগ্রাধিকার নিয়ন্ত্রণ করতে অন্যের বিরুদ্ধে এর উত্পাদন নিয়মের অবস্থান পরিবর্তন করে। উপরের ব্যাকরণটি দেখুন এবং নোট করুন যে এর জন্য উত্পাদনের নিয়ম *নীচে দেওয়া হয়েছে +এটি productআগে মূল্যায়ন করতে বাধ্য করবে sum। বাস্তবায়ন কেবল মূল্যায়নের সাথে প্যাটার্ন স্বীকৃতির সাথে একত্রিত হয় এবং এইভাবে উত্পাদন বিধিগুলি ঘনিষ্ঠভাবে আয়না করে।

expression_t eval_product(const char* symbols) {
    // product -> term | term "*" product
    expression_t term = eval_term(symbols);
    if (*term.context != '*')
        return term;

    expression_t product = eval_product(term.context + 1);
    return expression(term.value * product.value, product.context);
}

এখানে আমরা termপ্রথমে বিভক্ত হয়েছি এবং এটির যদি আমাদের উত্পাদনের নিয়মে* এটির বাম চিয়েস বাদে কোনও চরিত্র না থাকে তবে তা ফিরিয়ে দেব - এর পরে প্রতীকগুলি মূল্যায়ন করুন এবং এটি আমাদের উত্পাদনের নিয়মে ডান চিয়েস হিসাবে প্রত্যাবর্তন করুনterm.value * product.value term "*" product

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.